Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions admin/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
"start": "node dist/index.js"
},
"dependencies": {
"@hono/node-server": "^1.19.0",
"@hono/node-server": "^1.19.1",
"@hono/zod-validator": "^0.7.2",
"@t3-oss/env-core": "^0.13.8",
"better-auth": "^1.3.7",
"hono": "^4.9.4",
"better-auth": "^1.3.8",
"hono": "^4.9.6",
"qs": "^6.14.0",
"zod": "^4.0.17"
"zod": "^4.1.5"
},
"devDependencies": {
"@types/node": "^20.19.11",
"tsx": "^4.20.4",
"@types/node": "^20.19.13",
"tsx": "^4.20.5",
"typescript": "^5.9.2"
}
}
40 changes: 20 additions & 20 deletions apps/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,45 @@
"start": "node .output/server/index.mjs"
},
"dependencies": {
"@ai-sdk/openai": "^2.0.19",
"@ai-sdk/openai": "^2.0.24",
"@cerbos/grpc": "^0.22.1",
"@libsql/client": "^0.15.12",
"@mantine/core": "^8.2.5",
"@mantine/form": "^8.2.5",
"@mantine/hooks": "^8.2.5",
"@mantine/notifications": "^8.2.5",
"@libsql/client": "^0.15.14",
"@mantine/core": "^8.2.8",
"@mantine/form": "^8.2.8",
"@mantine/hooks": "^8.2.8",
"@mantine/notifications": "^8.2.8",
"@tabler/icons-react": "^3.34.1",
"@tanstack/react-query": "^5.85.5",
"@tanstack/react-router": "^1.131.27",
"@tanstack/react-query": "^5.87.1",
"@tanstack/react-router": "^1.131.35",
"@tanstack/react-router-with-query": "^1.130.17",
"@tanstack/react-start": "^1.131.27",
"ai": "^5.0.21",
"better-auth": "^1.3.7",
"@tanstack/react-start": "^1.131.35",
"ai": "^5.0.34",
"better-auth": "^1.3.8",
"clsx": "^2.1.1",
"drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.4",
"drizzle-orm": "^0.44.5",
"drizzle-zod": "^0.8.3",
"lucide-react": "^0.525.0",
"mantine-form-zod-resolver": "^1.3.0",
"openid-client": "^6.6.4",
"posthog-js": "^1.260.2",
"openid-client": "^6.7.1",
"posthog-js": "^1.261.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwind-preset-mantine": "^2.1.0",
"zod": "^4.0.17"
"zod": "^4.1.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.12",
"@types/node": "^22.17.2",
"@types/react": "^18.3.23",
"@tailwindcss/postcss": "^4.1.13",
"@types/node": "^22.18.1",
"@types/react": "^18.3.24",
"@types/react-dom": "^18.3.7",
"@vitejs/plugin-react": "^4.7.0",
"autoprefixer": "^10.4.21",
"dotenv": "^17.2.1",
"dotenv": "^17.2.2",
"postcss": "^8.5.6",
"postcss-preset-mantine": "^1.18.0",
"postcss-simple-vars": "^7.0.1",
"tailwindcss": "^4.1.12",
"tailwindcss": "^4.1.13",
"typescript": "^5.9.2",
"vite": "^6.3.5",
"vite-tsconfig-paths": "^5.1.4"
Expand Down
37 changes: 19 additions & 18 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@
"@hypr/plugin-sfx": "workspace:^",
"@hypr/plugin-task": "workspace:^",
"@hypr/plugin-template": "workspace:^",
"@hypr/plugin-windows": "workspace:^",
"@hypr/plugin-tracing": "workspace:^",
"@hypr/plugin-windows": "workspace:^",
"@hypr/tiptap": "workspace:^",
"@hypr/ui": "workspace:^",
"@hypr/utils": "workspace:^",
"@lingui/core": "^5.4.1",
"@lingui/react": "^5.4.1",
"@modelcontextprotocol/sdk": "^1.17.3",
"@lobehub/icons": "^2.31.0",
"@modelcontextprotocol/sdk": "^1.17.5",
"@mux/mux-player-react": "^3.5.3",
"@mux/mux-video": "^0.26.1",
"@radix-ui/react-dialog": "^1.1.15",
Expand All @@ -53,21 +54,21 @@
"@react-pdf/renderer": "^4.3.0",
"@remixicon/react": "^4.6.0",
"@sentry/react": "^8.55.0",
"@tanstack/react-query": "^5.85.5",
"@tanstack/react-query-devtools": "^5.85.5",
"@tanstack/react-router": "^1.131.27",
"@tanstack/react-router-devtools": "^1.131.27",
"@tanstack/zod-adapter": "^1.131.27",
"@tanstack/react-query": "^5.87.1",
"@tanstack/react-query-devtools": "^5.87.1",
"@tanstack/react-router": "^1.131.35",
"@tanstack/react-router-devtools": "^1.131.35",
"@tanstack/zod-adapter": "^1.131.35",
"@tauri-apps/api": "^2.8.0",
"@tauri-apps/plugin-autostart": "^2.5.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.3.3",
"@tauri-apps/plugin-dialog": "^2.4.0",
"@tauri-apps/plugin-fs": "^2.4.2",
"@tauri-apps/plugin-http": "^2.5.2",
"@tauri-apps/plugin-opener": "^2.5.0",
"@tauri-apps/plugin-os": "^2.3.1",
"@tauri-apps/plugin-process": "^2.3.0",
"@tauri-apps/plugin-shell": "^2.3.0",
"@tauri-apps/plugin-shell": "^2.3.1",
"@tauri-apps/plugin-store": "^2.4.0",
"@tauri-apps/plugin-updater": "^2.9.0",
"@types/lodash-es": "^4.17.12",
Expand All @@ -79,7 +80,7 @@
"html-pdf": "^3.0.1",
"html2canvas": "^1.4.1",
"html2pdf.js": "^0.10.3",
"jspdf": "^3.0.1",
"jspdf": "^3.0.2",
"lodash-es": "^4.17.21",
"lucide-react": "^0.525.0",
"motion": "^11.18.2",
Expand All @@ -98,22 +99,22 @@
"tauri-plugin-keygen-api": "github:bagindo/tauri-plugin-keygen#v2",
"tauri-plugin-sentry-api": "^0.4.1",
"tippy.js": "^6.3.7",
"zod": "^4.0.17",
"zod": "^4.1.5",
"zustand": "^5.0.8"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
"@eslint/js": "^9.35.0",
"@lingui/babel-plugin-lingui-macro": "^5.4.1",
"@lingui/cli": "^5.4.1",
"@lingui/vite-plugin": "^5.4.1",
"@mux/mux-player": "^3.5.3",
"@tanstack/router-plugin": "^1.131.27",
"@tauri-apps/cli": "^2.8.1",
"@tanstack/router-plugin": "^1.131.35",
"@tauri-apps/cli": "^2.8.4",
"@types/diff": "^8.0.0",
"@types/html-pdf": "^3.0.3",
"@types/jspdf": "^2.0.0",
"@types/node": "^22.17.2",
"@types/react": "^18.3.23",
"@types/node": "^22.18.1",
"@types/react": "^18.3.24",
"@types/react-dom": "^18.3.7",
"@types/react-grid-layout": "^1.3.5",
"@vitejs/plugin-react": "^4.7.0",
Expand All @@ -124,14 +125,14 @@
"@wdio/spec-reporter": "^8.43.0",
"@wdio/types": "^8.41.0",
"autoprefixer": "^10.4.21",
"eslint": "^9.33.0",
"eslint": "^9.35.0",
"eslint-plugin-lingui": "^0.9.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^15.15.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17",
"typescript": "^5.9.2",
"typescript-eslint": "^8.40.0",
"typescript-eslint": "^8.42.0",
"vite": "^5.4.19",
"vite-multiple-assets": "^2.2.5",
"vitest": "^3.2.4"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { LmStudio } from "@lobehub/icons";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { openPath } from "@tauri-apps/plugin-opener";
import { open } from "@tauri-apps/plugin-shell";
import { CloudIcon, DownloadIcon, FolderIcon } from "lucide-react";
import { useEffect } from "react";

import { useLicense } from "@/hooks/use-license";
import { commands as localLlmCommands, type SupportedModel } from "@hypr/plugin-local-llm";
import { commands as localLlmCommands, type CustomModelInfo, type ModelSelection } from "@hypr/plugin-local-llm";
import { commands as windowsCommands } from "@hypr/plugin-windows";
import { Button } from "@hypr/ui/components/ui/button";
import { cn } from "@hypr/ui/lib/utils";
Expand All @@ -32,44 +33,64 @@ export function LLMLocalView({
const isPro = !!getLicense.data?.valid;
const queryClient = useQueryClient();

const currentLLMModel = useQuery({
queryKey: ["current-llm-model"],
queryFn: () => localLlmCommands.getCurrentModel(),
const currentModelSelection = useQuery({
queryKey: ["current-model-selection"],
queryFn: () => localLlmCommands.getCurrentModelSelection(),
});

const customModels = useQuery({
queryKey: ["custom-models"],
queryFn: () => localLlmCommands.listCustomModels(),
refetchInterval: 5000,
});

const handleShowFileLocation = async () => {
localLlmCommands.modelsDir().then((path) => openPath(path));
};

useEffect(() => {
// Auto-select current local model when switching away from remote endpoints
if (currentLLMModel.data && !customLLMEnabled.data) {
setSelectedLLMModel(currentLLMModel.data);
if (currentModelSelection.data && !customLLMEnabled.data) {
const selection = currentModelSelection.data;
if (selection.type === "Predefined") {
setSelectedLLMModel(selection.content.key);
} else if (selection.type === "Custom") {
setSelectedLLMModel(`custom-${selection.content.path}`);
}
}
}, [currentLLMModel.data, customLLMEnabled.data, setSelectedLLMModel]);
}, [currentModelSelection.data, customLLMEnabled.data, setSelectedLLMModel]);

const handleLocalModelSelection = async (model: LLMModel) => {
if (model.available && model.downloaded) {
// Update UI state first for immediate feedback
setSelectedLLMModel(model.key);

// Then update backend state
await localLlmCommands.setCurrentModel(model.key as SupportedModel);
queryClient.invalidateQueries({ queryKey: ["current-llm-model"] });
const selection: ModelSelection = { type: "Predefined", content: { key: model.key } };
await localLlmCommands.setCurrentModelSelection(selection);
queryClient.invalidateQueries({ queryKey: ["current-model-selection"] });

// Disable BOTH HyprCloud and custom when selecting local
setCustomLLMEnabledMutation.mutate(false);
setHyprCloudEnabledMutation.mutate(false);
setOpenAccordion(null);

// Restart server for local model
localLlmCommands.restartServer();
}
};

const handleCustomModelSelection = async (customModel: CustomModelInfo) => {
setSelectedLLMModel(`custom-${customModel.path}`);

const selection: ModelSelection = { type: "Custom", content: { path: customModel.path } };
await localLlmCommands.setCurrentModelSelection(selection);
queryClient.invalidateQueries({ queryKey: ["current-model-selection"] });

setCustomLLMEnabledMutation.mutate(false);
setHyprCloudEnabledMutation.mutate(false);
setOpenAccordion(null);

localLlmCommands.restartServer();
};

Comment on lines +78 to +91
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Custom selection flow—LGTM; verify command availability.
Ensure localLlmCommands.restartServer exists/exports in the plugin.


🏁 Script executed:

#!/bin/bash
rg -nP -C2 '\brestartServer\b' -- apps plugins packages || true

Length of output: 1798


Implement and export restartServer in localLlmCommands
Ripgrep across apps, plugins, and packages only shows calls to localLlmCommands.restartServer()—no definition or export exists. Add a restartServer implementation and export it from the plugin’s command interface to prevent runtime errors.

🤖 Prompt for AI Agents
In apps/desktop/src/components/settings/components/ai/llm-local-view.tsx around
lines 78 to 91, the call localLlmCommands.restartServer() has no implementation
or export; add a restartServer method to the localLlmCommands plugin command
surface, implement it to asynchronously restart the local LLM server (e.g.,
await stopServer(); await startServer(); handle and surface errors), ensure it
returns a Promise<void>, add the method to the plugin's TypeScript command
interface/type, and export it from the plugin entry so callers like this
component can import and invoke it without runtime errors.

const handleHyprCloudSelection = () => {
setSelectedLLMModel("hyprcloud");
// Just use the configureCustomEndpoint which handles the flags
configureCustomEndpoint({
provider: "hyprcloud",
api_base: "https://pro.hyprnote.com",
Expand All @@ -80,15 +101,12 @@ export function LLMLocalView({
};

const isHyprCloudSelected = hyprCloudEnabled.data;

// Base button class to remove default styling
const buttonResetClass = "appearance-none border-0 outline-0 bg-transparent p-0 m-0 font-inherit text-left w-full";

return (
<div className="space-y-6">
<div className="max-w-2xl">
<div className="space-y-2">
{/* HyprCloud Option */}
<div
className={cn(
"group relative p-3 rounded-lg border-2 transition-all",
Expand All @@ -99,7 +117,6 @@ export function LLMLocalView({
>
<div className="flex items-center justify-between">
<div className="relative flex-1">
{/* Main button */}
<button
onClick={handleHyprCloudSelection}
disabled={!isPro}
Expand Down Expand Up @@ -156,7 +173,6 @@ export function LLMLocalView({
</div>
</div>

{/* Separator */}
<div className="relative py-2">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-200"></div>
Expand All @@ -166,7 +182,6 @@ export function LLMLocalView({
</div>
</div>

{/* Local Models */}
{llmModelsState.map((model) => (
<button
key={model.key}
Expand Down Expand Up @@ -255,6 +270,48 @@ export function LLMLocalView({
</div>
</button>
))}

{customModels.data && customModels.data.length > 0 && (
<>
<div className="relative py-2">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-200"></div>
</div>
<div className="relative flex justify-center text-xs">
<span className="px-2 bg-gray-50 text-gray-500">custom GGUF models</span>
</div>
</div>

{customModels.data.map((customModel) => {
const isSelected = selectedLLMModel === `custom-${customModel.path}` && !customLLMEnabled.data;
return (
<button
key={customModel.path}
onClick={() => handleCustomModelSelection(customModel)}
className={cn(
buttonResetClass,
"group relative p-3 rounded-lg border-2 transition-all flex items-center justify-between",
isSelected
? "border-solid border-blue-500 bg-blue-50 cursor-pointer"
: "border-dashed border-gray-300 hover:border-gray-400 bg-white cursor-pointer",
)}
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-4">
<div className="min-w-0">
<h3 className="font-semibold text-base text-gray-900 flex items-center gap-2">
<LmStudio size={14} />
{customModel.name}
</h3>
</div>
</div>
</div>
<span className="text-xs text-gray-500">{customModel.path.split("/").slice(-1)[0]}</span>
</button>
);
})}
</>
)}
</div>
</div>
</div>
Expand Down
Loading
Loading