-
Notifications
You must be signed in to change notification settings - Fork 376
fix: Auto-disable HyprCloud when license expires #1368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: Auto-disable HyprCloud when license expires #1368
Conversation
- Automatically disable HyprCloud and fall back to HyprLLM if avalaible, otherwise to local models when license becomes invalid Fixes fastrepl#1366
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughAdded React Query hooks to read and update HyprCloud enabled state, plus a side-effect that, when the license is not valid (non-pro), disables HyprCloud and the custom LLM. On successful HyprCloud disable, the enabled state is refetched. Existing license refresh logic remains. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant LicenseComponent
participant LicenseAPI as License Query
participant Connector as connectorCommands
note over LicenseComponent: Mount/Update
LicenseComponent->>LicenseAPI: getLicense (valid?)
LicenseComponent->>Connector: getHyprcloudEnabled()
Connector-->>LicenseComponent: hyprCloudEnabled
alt License not valid AND HyprCloud enabled
LicenseComponent->>Connector: setHyprcloudEnabled(false)
Connector-->>LicenseComponent: success
LicenseComponent->>Connector: refetch getHyprcloudEnabled()
rect rgba(230,240,255,0.6)
note right of LicenseComponent: Also disable Custom LLM
LicenseComponent->>Connector: setCustomLlmEnabled(false)
Connector-->>LicenseComponent: success
end
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: .coderabbit.yaml 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 issues found across 1 file
React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai
to give feedback, ask questions, or re-run the review.
}, [currentLLMModel.data, customLLMEnabled.data, setSelectedLLMModel]); | ||
|
||
// Auto-disable HyprCloud when license expires | ||
useEffect(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Business logic for license enforcement and model fallback is incorrectly placed within a UI component. This logic only runs when the component is mounted, meaning the HyprCloud
feature will not be disabled if a user's license expires unless they happen to navigate to this specific settings page.
Prompt for AI agents
Address the following comment on apps/desktop/src/components/settings/components/ai/llm-local-view.tsx at line 51:
<comment>Business logic for license enforcement and model fallback is incorrectly placed within a UI component. This logic only runs when the component is mounted, meaning the `HyprCloud` feature will not be disabled if a user's license expires unless they happen to navigate to this specific settings page.</comment>
<file context>
@@ -47,6 +47,32 @@ export function LLMLocalView({
}
}, [currentLLMModel.data, customLLMEnabled.data, setSelectedLLMModel]);
+ // Auto-disable HyprCloud when license expires
+ useEffect(() => {
+ if (!isPro && hyprCloudEnabled.data) {
+ setHyprCloudEnabledMutation.mutate(false);
</file context>
const hyprModel = llmModelsState.find(m => m.key === "HyprLLM" && m.downloaded && m.available); | ||
if (hyprModel) { | ||
setSelectedLLMModel("HyprLLM"); | ||
localLlmCommands.setCurrentModel("HyprLLM"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle the Promise returned by setCurrentModel to avoid unhandled rejections
Prompt for AI agents
Address the following comment on apps/desktop/src/components/settings/components/ai/llm-local-view.tsx at line 63:
<comment>Handle the Promise returned by setCurrentModel to avoid unhandled rejections</comment>
<file context>
@@ -47,6 +47,32 @@ export function LLMLocalView({
}
}, [currentLLMModel.data, customLLMEnabled.data, setSelectedLLMModel]);
+ // Auto-disable HyprCloud when license expires
+ useEffect(() => {
+ if (!isPro && hyprCloudEnabled.data) {
+ setHyprCloudEnabledMutation.mutate(false);
+ setCustomLLMEnabledMutation.mutate(false);
+ // Fall back to current local model if available
</file context>
localLlmCommands.setCurrentModel("HyprLLM"); | |
localLlmCommands.setCurrentModel("HyprLLM").catch(console.error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
apps/desktop/src/components/settings/components/ai/llm-local-view.tsx (2)
247-249
: Remove debugging console.log statements.These console.log statements appear to be leftover debugging code and should be removed for production.
- console.log("model download clicked"); e.stopPropagation(); - console.log("model download clicked 2"); handleModelDownload(model.key);
74-74
: Consider optimizing dependency array for better performance.The effect has a large dependency array that could cause frequent re-runs. Consider memoizing the
llmModelsState
dependency or restructuring the effect to reduce unnecessary executions.Consider extracting the model finding logic into a useMemo hook:
+ const availableModels = useMemo(() => { + return { + hyprModel: llmModelsState.find(m => m.key === "HyprLLM" && m.downloaded && m.available), + anyModel: llmModelsState.find(m => m.downloaded && m.available) + }; + }, [llmModelsState]); // Auto-disable HyprCloud when license expires useEffect(() => { if (!isPro && hyprCloudEnabled.data) { setHyprCloudEnabledMutation.mutate(false); setCustomLLMEnabledMutation.mutate(false); // Fall back to current local model if available if (currentLLMModel.data) { setSelectedLLMModel(currentLLMModel.data); } else { // Fall back to HyprLLM (preferred default) if downloaded - const hyprModel = llmModelsState.find(m => m.key === "HyprLLM" && m.downloaded && m.available); + const hyprModel = availableModels.hyprModel; if (hyprModel) { setSelectedLLMModel("HyprLLM"); localLlmCommands.setCurrentModel("HyprLLM"); } else { // Final fallback to any available downloaded model - const downloadedModel = llmModelsState.find(m => m.downloaded && m.available); + const downloadedModel = availableModels.anyModel; if (downloadedModel) { setSelectedLLMModel(downloadedModel.key); localLlmCommands.setCurrentModel(downloadedModel.key as SupportedModel); } } } } - }, [isPro, hyprCloudEnabled.data, setHyprCloudEnabledMutation, setCustomLLMEnabledMutation, currentLLMModel.data, setSelectedLLMModel, llmModelsState]); + }, [isPro, hyprCloudEnabled.data, setHyprCloudEnabledMutation, setCustomLLMEnabledMutation, currentLLMModel.data, setSelectedLLMModel, availableModels]);
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/desktop/src/components/settings/components/ai/llm-local-view.tsx
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}
⚙️ CodeRabbit Configuration File
**/*.{js,ts,tsx,rs}
: 1. No error handling.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".
Files:
apps/desktop/src/components/settings/components/ai/llm-local-view.tsx
🔇 Additional comments (1)
apps/desktop/src/components/settings/components/ai/llm-local-view.tsx (1)
50-74
: LGTM! License expiration handling implemented correctly.The effect properly implements the automatic fallback behavior when the license expires. The logic correctly:
- Disables both HyprCloud and custom LLM toggles
- Falls back to current local model if available
- Prefers HyprLLM as the default local model
- Falls back to any available downloaded model as final option
- Updates both UI state and backend model selection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
apps/desktop/src/hooks/use-llm-settings.ts (1)
11-16
: Optional: invalidate queries instead of manual refetch for better consistencyUsing
queryClient.invalidateQueries
decouples the mutation from this particular hook instance and aligns with React Query’s recommended flow, especially if multiple components subscribe to the same key.const setHyprCloudEnabledMutation = useMutation({ mutationFn: (enabled: boolean) => connectorCommands.setHyprcloudEnabled(enabled), onSuccess: () => { - hyprCloudEnabled.refetch(); + // Prefer invalidation to let all subscribers update consistently + queryClient.invalidateQueries({ queryKey: ["hypr-cloud-enabled"] }); }, }); @@ const setCustomLLMEnabledMutation = useMutation({ mutationFn: (enabled: boolean) => connectorCommands.setCustomLlmEnabled(enabled), onSuccess: () => { - customLLMEnabled.refetch(); + queryClient.invalidateQueries({ queryKey: ["custom-llm-enabled"] }); }, });Additions needed outside the selected ranges:
- import { useMutation, useQuery } from "@tanstack/react-query"; + import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";// Inside useLlmSettings() const queryClient = useQueryClient();Also applies to: 23-28
apps/desktop/src/components/license.tsx (2)
19-19
: Simplify isPro logic to match useLicense semantics
useLicense().getLicense
already returnsnull
when invalid.Boolean(getLicense.data)
is sufficient and avoids optional chaining on.valid
.- const isPro = !!getLicense.data?.valid; + const isPro = Boolean(getLicense.data);
21-25
: Debounce repeated disable mutations while pendingGuarding with
isPending
prevents repeated.mutate(false)
calls across renders if the query state hasn’t updated yet (e.g., transient failures or latency), reducing churn.- if (!isPro && hyprCloudEnabled.data) { - setHyprCloudEnabledMutation.mutate(false); - setCustomLLMEnabledMutation.mutate(false); - - } + if ( + !isPro + && hyprCloudEnabled.data === true + && !setHyprCloudEnabledMutation.isPending + && !setCustomLLMEnabledMutation.isPending + ) { + setHyprCloudEnabledMutation.mutate(false); + setCustomLLMEnabledMutation.mutate(false); + }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
apps/desktop/src/components/license.tsx
(1 hunks)apps/desktop/src/hooks/use-llm-settings.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}
⚙️ CodeRabbit Configuration File
**/*.{js,ts,tsx,rs}
: 1. No error handling.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".
Files:
apps/desktop/src/hooks/use-llm-settings.ts
apps/desktop/src/components/license.tsx
🧬 Code Graph Analysis (1)
apps/desktop/src/components/license.tsx (2)
apps/desktop/src/hooks/use-license.ts (1)
useLicense
(9-105)apps/desktop/src/hooks/use-llm-settings.ts (1)
useLlmSettings
(5-36)
🔇 Additional comments (3)
apps/desktop/src/hooks/use-llm-settings.ts (1)
30-36
: Solid hook interfaceThe hook’s minimal API (query results + mutation objects) is clear and composes well with React Query-driven UIs.
apps/desktop/src/components/license.tsx (2)
22-23
: Confirm product intent: disabling Custom LLM when not ProIssue #1366 and the PR title only call out auto-disabling HyprCloud. If Custom LLM is also a Pro-only feature, this is correct; otherwise, this might unintentionally remove a valid local option.
Would you confirm whether Custom LLM should be gated behind Pro and auto-disabled on license expiry?
17-26
: Missing fallback logic after HyprCloud auto-disableI searched the codebase and didn’t find any handler that re-selects a local LLM when HyprCloud is turned off. In apps/desktop/src/components/license.tsx (lines 17–26), the effect only calls:
setHyprCloudEnabledMutation.mutate(false); setCustomLLMEnabledMutation.mutate(false);but never invokes the imported
localLlmCommands
(or any setter) to pick a fallback model.Please implement or verify a fallback path that runs immediately after disabling HyprCloud, selecting the next available model in this order:
- Current local model (if still present)
- HyprLLM (if downloaded)
- Any other downloaded local model
Locations to update:
- apps/desktop/src/components/license.tsx (around lines 17–26): after disabling HyprCloud/Custom LLM, call into
localLlmCommands
or your model-selection hook.
const hyprCloudEnabled = useQuery({ | ||
queryKey: ["hypr-cloud-enabled"], | ||
queryFn: () => connectorCommands.getHyprcloudEnabled(), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Type safety: explicitly type the useQuery data as boolean
Without generics, useQuery
defaults to unknown
for data
, which can cause TS errors at call sites (e.g., hyprCloudEnabled.data
used as a boolean in license.tsx). Annotate the data type as boolean
for both queries.
- const hyprCloudEnabled = useQuery({
+ const hyprCloudEnabled = useQuery<boolean>({
queryKey: ["hypr-cloud-enabled"],
queryFn: () => connectorCommands.getHyprcloudEnabled(),
});
@@
- const customLLMEnabled = useQuery({
+ const customLLMEnabled = useQuery<boolean>({
queryKey: ["custom-llm-enabled"],
queryFn: () => connectorCommands.getCustomLlmEnabled(),
});
Also applies to: 18-21
🤖 Prompt for AI Agents
In apps/desktop/src/hooks/use-llm-settings.ts around lines 6-9 and 18-21, the
useQuery calls lack a generic type so their .data is typed as unknown; update
both useQuery invocations to use useQuery<boolean> (e.g., useQuery<boolean>({
queryKey: [...], queryFn: () => connectorCommands.getHyprcloudEnabled(), })) and
ensure the queryFn returns a boolean (cast the return if necessary) so callers
can safely use hyprCloudEnabled.data as a boolean.
Fixes Issue #1366
Summary by cubic
Automatically disables HyprCloud when the license expires and switches to a local model to keep things working: current local model first, then HyprLLM if downloaded, then any downloaded model. Fixes #1366.