diff --git a/main.js b/main.js
index eeeed4445..426c74a3a 100644
--- a/main.js
+++ b/main.js
@@ -740,13 +740,30 @@ async function startApp() {
debugLogger.debug("Parakeet startup init error (non-fatal)", { error: err.message });
});
- if (process.env.REASONING_PROVIDER === "local" && process.env.LOCAL_REASONING_MODEL) {
+ // TODO: drop legacy REASONING_PROVIDER / LOCAL_REASONING_MODEL fallbacks after 2 releases.
+ const cleanupProvider = process.env.CLEANUP_PROVIDER || process.env.REASONING_PROVIDER;
+ const cleanupLocalModel =
+ process.env.LOCAL_CLEANUP_MODEL || process.env.LOCAL_REASONING_MODEL;
+ if (cleanupProvider === "local" && cleanupLocalModel) {
const modelManager = require("./src/helpers/modelManagerBridge").default;
- modelManager.prewarmServer(process.env.LOCAL_REASONING_MODEL).catch((err) => {
+ modelManager.prewarmServer(cleanupLocalModel).catch((err) => {
debugLogger.debug("llama-server pre-warm error (non-fatal)", { error: err.message });
});
}
+ if (
+ process.env.DICTATION_AGENT_PROVIDER === "local" &&
+ process.env.LOCAL_DICTATION_AGENT_MODEL &&
+ process.env.LOCAL_DICTATION_AGENT_MODEL !== cleanupLocalModel
+ ) {
+ const modelManager = require("./src/helpers/modelManagerBridge").default;
+ modelManager.prewarmServer(process.env.LOCAL_DICTATION_AGENT_MODEL).catch((err) => {
+ debugLogger.debug("dictation-agent llama-server pre-warm error (non-fatal)", {
+ error: err.message,
+ });
+ });
+ }
+
// Auto-download diarization models if binary is available
if (
diarizationManager.getBinaryPath() &&
diff --git a/src/components/ControlPanel.tsx b/src/components/ControlPanel.tsx
index e2596715a..724980716 100644
--- a/src/components/ControlPanel.tsx
+++ b/src/components/ControlPanel.tsx
@@ -79,7 +79,7 @@ export default function ControlPanel() {
const {
useLocalWhisper,
localTranscriptionProvider,
- useReasoningModel,
+ useCleanupModel,
setUseLocalWhisper,
setCloudTranscriptionMode,
} = useSettings();
@@ -242,7 +242,7 @@ export default function ControlPanel() {
if (status?.gpuInfo.hasNvidiaGpu && !status.downloaded) results.cuda = true;
} catch {}
}
- if (useReasoningModel) {
+ if (useCleanupModel) {
try {
const [gpu, vulkan] = await Promise.all([
window.electronAPI?.detectVulkanGpu?.(),
@@ -254,7 +254,7 @@ export default function ControlPanel() {
setGpuAccelAvailable(results);
};
detect();
- }, [useLocalWhisper, localTranscriptionProvider, useReasoningModel, gpuBannerDismissed]);
+ }, [useLocalWhisper, localTranscriptionProvider, useCleanupModel, gpuBannerDismissed]);
useEffect(() => {
const cleanup = window.electronAPI?.onNavigateToMeetingNote?.((data) => {
@@ -447,17 +447,17 @@ export default function ControlPanel() {
let finalTranscription = result.transcription;
// Apply AI reasoning if enabled
- if (useReasoningModel) {
+ if (useCleanupModel) {
try {
const [
{ default: ReasoningService },
- { getEffectiveReasoningModel, isCloudReasoningMode },
+ { getEffectiveCleanupModel, isCloudCleanupMode },
] = await Promise.all([
import("../services/ReasoningService"),
import("../stores/settingsStore"),
]);
- const model = getEffectiveReasoningModel();
- const isCloud = isCloudReasoningMode();
+ const model = getEffectiveCleanupModel();
+ const isCloud = isCloudCleanupMode();
if (model || isCloud) {
const agentName = localStorage.getItem("agentName") || null;
const reasonedText = await ReasoningService.processText(rawText, model, agentName);
@@ -493,7 +493,7 @@ export default function ControlPanel() {
});
}
},
- [toast, t, useReasoningModel]
+ [toast, t, useCleanupModel]
);
const handleUpdateClick = async () => {
@@ -791,7 +791,7 @@ export default function ControlPanel() {
setShowCloudMigrationBanner={setShowCloudMigrationBanner}
aiCTADismissed={aiCTADismissed}
setAiCTADismissed={setAiCTADismissed}
- useReasoningModel={useReasoningModel}
+ useCleanupModel={useCleanupModel}
copyToClipboard={copyToClipboard}
deleteTranscription={deleteTranscription}
clearAllTranscriptions={clearAllTranscriptions}
diff --git a/src/components/HistoryView.tsx b/src/components/HistoryView.tsx
index 7fe6acfd1..a1b2f222f 100644
--- a/src/components/HistoryView.tsx
+++ b/src/components/HistoryView.tsx
@@ -19,7 +19,7 @@ interface HistoryViewProps {
setShowCloudMigrationBanner: (show: boolean) => void;
aiCTADismissed: boolean;
setAiCTADismissed: (dismissed: boolean) => void;
- useReasoningModel: boolean;
+ useCleanupModel: boolean;
copyToClipboard: (text: string) => void;
deleteTranscription: (id: number) => void;
clearAllTranscriptions: () => void;
@@ -36,7 +36,7 @@ export default function HistoryView({
setShowCloudMigrationBanner,
aiCTADismissed,
setAiCTADismissed,
- useReasoningModel,
+ useCleanupModel,
copyToClipboard,
deleteTranscription,
clearAllTranscriptions,
@@ -111,7 +111,7 @@ export default function HistoryView({
)}
- {!useReasoningModel && !aiCTADismissed && (
+ {!useCleanupModel && !aiCTADismissed && (
);
}
-
-export function MeetingReasoningPanel() {
- const { t } = useTranslation();
- const startOnboarding = useStartOnboarding();
-
- const {
- isSignedIn,
- meetingReasoningMode,
- setMeetingReasoningMode,
- meetingReasoningProvider,
- setMeetingReasoningProvider,
- meetingReasoningModel,
- setMeetingReasoningModel,
- setMeetingCloudReasoningMode,
- meetingCloudReasoningBaseUrl,
- setMeetingCloudReasoningBaseUrl,
- meetingRemoteReasoningUrl,
- setMeetingRemoteReasoningUrl,
- openaiApiKey,
- setOpenaiApiKey,
- anthropicApiKey,
- setAnthropicApiKey,
- geminiApiKey,
- setGeminiApiKey,
- groqApiKey,
- setGroqApiKey,
- customReasoningApiKey,
- setCustomReasoningApiKey,
- } = useSettingsStore();
-
- const aiModes: InferenceModeOption[] = [
- {
- id: "openwhispr",
- label: t("settingsPage.aiModels.modes.openwhispr"),
- description: t("settingsPage.aiModels.modes.openwhisprDesc"),
- icon: ,
- disabled: !isSignedIn,
- badge: !isSignedIn ? t("common.freeAccountRequired") : undefined,
- },
- {
- id: "providers",
- label: t("settingsPage.aiModels.modes.providers"),
- description: t("settingsPage.aiModels.modes.providersDesc"),
- icon: ,
- },
- {
- id: "local",
- label: t("settingsPage.aiModels.modes.local"),
- description: t("settingsPage.aiModels.modes.localDesc"),
- icon: ,
- },
- {
- id: "self-hosted",
- label: t("settingsPage.aiModels.modes.selfHosted"),
- description: t("settingsPage.aiModels.modes.selfHostedDesc"),
- icon: ,
- },
- {
- id: "enterprise",
- label: t("settingsPage.aiModels.modes.enterprise"),
- description: t("settingsPage.aiModels.modes.enterpriseDesc"),
- icon: ,
- },
- ];
-
- const handleReasoningModeSelect = (mode: InferenceMode) => {
- if (mode === "openwhispr" && !isSignedIn) {
- startOnboarding();
- return;
- }
- if (mode === meetingReasoningMode) return;
- setMeetingReasoningMode(mode);
- setMeetingCloudReasoningMode(mode === "openwhispr" ? "openwhispr" : "byok");
- };
-
- const renderReasoningSelector = (mode?: "cloud" | "local") => (
-
- );
-
- return (
-
-
-
- {meetingReasoningMode === "providers" && renderReasoningSelector("cloud")}
- {meetingReasoningMode === "local" && renderReasoningSelector("local")}
- {meetingReasoningMode === "self-hosted" && (
-
- )}
- {meetingReasoningMode === "enterprise" && (
-
- )}
-
- );
-}
diff --git a/src/components/ui/PromptStudio.tsx b/src/components/ui/PromptStudio.tsx
index 63dde076c..e5956778d 100644
--- a/src/components/ui/PromptStudio.tsx
+++ b/src/components/ui/PromptStudio.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect } from "react";
+import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "./button";
import { Textarea } from "./textarea";
@@ -19,11 +19,12 @@ import { useAgentName } from "../../utils/agentName";
import ReasoningService from "../../services/ReasoningService";
import { getModelProvider } from "../../models/ModelRegistry";
import logger from "../../utils/logger";
-import { UNIFIED_SYSTEM_PROMPT } from "../../config/prompts";
-import { useSettingsStore, selectIsCloudReasoningMode } from "../../stores/settingsStore";
+import { getDefaultPromptText, type PromptKind } from "../../config/prompts";
+import { useSettingsStore, selectIsCloudCleanupMode } from "../../stores/settingsStore";
interface PromptStudioProps {
className?: string;
+ kind?: PromptKind;
}
type ProviderConfig = {
@@ -41,27 +42,14 @@ const PROVIDER_CONFIG: Record = {
custom: {
label: "Custom endpoint",
apiKeyStorageKey: "openaiApiKey",
- baseStorageKey: "cloudReasoningBaseUrl",
+ baseStorageKey: "cleanupCloudBaseUrl",
},
local: { label: "Local" },
};
-function getCurrentPrompt(): string {
- const customPrompt = localStorage.getItem("customUnifiedPrompt");
- if (customPrompt) {
- try {
- return JSON.parse(customPrompt);
- } catch {
- return UNIFIED_SYSTEM_PROMPT;
- }
- }
- return UNIFIED_SYSTEM_PROMPT;
-}
-
-export default function PromptStudio({ className = "" }: PromptStudioProps) {
+export default function PromptStudio({ className = "", kind = "cleanup" }: PromptStudioProps) {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState<"current" | "edit" | "test">("current");
- const [editedPrompt, setEditedPrompt] = useState(UNIFIED_SYSTEM_PROMPT);
const [testText, setTestText] = useState(() => t("promptStudio.defaultTestInput"));
const [testResult, setTestResult] = useState("");
const [isLoading, setIsLoading] = useState(false);
@@ -69,38 +57,19 @@ export default function PromptStudio({ className = "" }: PromptStudioProps) {
const { alertDialog, showAlertDialog, hideAlertDialog } = useDialogs();
const { agentName } = useAgentName();
+ const uiLanguage = useSettingsStore((s) => s.uiLanguage);
- const effectiveModel = useSettingsStore((s) => s.reasoningModel);
- const isCloudMode = useSettingsStore(selectIsCloudReasoningMode);
- const useReasoningModel = useSettingsStore((s) => s.useReasoningModel);
- const reasoningModel = useSettingsStore((s) => s.reasoningModel);
-
- useEffect(() => {
- const legacyPrompts = localStorage.getItem("customPrompts");
- if (legacyPrompts && !localStorage.getItem("customUnifiedPrompt")) {
- try {
- const parsed = JSON.parse(legacyPrompts);
- if (parsed.agent) {
- localStorage.setItem("customUnifiedPrompt", JSON.stringify(parsed.agent));
- localStorage.removeItem("customPrompts");
- }
- } catch (e) {
- logger.error("Failed to migrate legacy custom prompts", { error: e }, "prompts");
- }
- }
+ const isCloudMode = useSettingsStore(selectIsCloudCleanupMode);
+ const useCleanupModel = useSettingsStore((s) => s.useCleanupModel);
+ const cleanupModel = useSettingsStore((s) => s.cleanupModel);
- const customPrompt = localStorage.getItem("customUnifiedPrompt");
- if (customPrompt) {
- try {
- setEditedPrompt(JSON.parse(customPrompt));
- } catch (error) {
- logger.error("Failed to load custom prompt", { error }, "prompts");
- }
- }
- }, []);
+ const customPrompt = useSettingsStore((s) => s.customPrompts[kind]);
+ const setCustomPrompt = useSettingsStore((s) => s.setCustomPrompt);
+ const defaultPrompt = getDefaultPromptText(kind, uiLanguage);
+ const [editedPrompt, setEditedPrompt] = useState(customPrompt || defaultPrompt);
const savePrompt = () => {
- localStorage.setItem("customUnifiedPrompt", JSON.stringify(editedPrompt));
+ setCustomPrompt(kind, editedPrompt);
showAlertDialog({
title: t("promptStudio.dialogs.saved.title"),
description: t("promptStudio.dialogs.saved.description"),
@@ -108,8 +77,8 @@ export default function PromptStudio({ className = "" }: PromptStudioProps) {
};
const resetToDefault = () => {
- setEditedPrompt(UNIFIED_SYSTEM_PROMPT);
- localStorage.removeItem("customUnifiedPrompt");
+ setEditedPrompt(defaultPrompt);
+ setCustomPrompt(kind, "");
showAlertDialog({
title: t("promptStudio.dialogs.reset.title"),
description: t("promptStudio.dialogs.reset.description"),
@@ -129,47 +98,47 @@ export default function PromptStudio({ className = "" }: PromptStudioProps) {
setTestResult("");
try {
- const reasoningProvider = isCloudMode
+ const cleanupProvider = isCloudMode
? "openwhispr"
- : reasoningModel
- ? getModelProvider(reasoningModel)
+ : cleanupModel
+ ? getModelProvider(cleanupModel)
: "openai";
logger.debug(
"PromptStudio test starting",
{
- useReasoningModel,
+ useCleanupModel,
isCloudMode,
- reasoningModel,
- reasoningProvider,
+ cleanupModel,
+ cleanupProvider,
testTextLength: testText.length,
agentName,
},
"prompt-studio"
);
- if (!useReasoningModel) {
+ if (!useCleanupModel) {
setTestResult(t("promptStudio.test.disabledReasoning"));
return;
}
- if (!isCloudMode && !reasoningModel) {
+ if (!isCloudMode && !cleanupModel) {
setTestResult(t("promptStudio.test.noModelSelected"));
return;
}
if (!isCloudMode) {
- const providerConfig = PROVIDER_CONFIG[reasoningProvider] || {
- label: reasoningProvider.charAt(0).toUpperCase() + reasoningProvider.slice(1),
+ const providerConfig = PROVIDER_CONFIG[cleanupProvider] || {
+ label: cleanupProvider.charAt(0).toUpperCase() + cleanupProvider.slice(1),
};
if (providerConfig.baseStorageKey) {
- const baseUrl = (useSettingsStore.getState().cloudReasoningBaseUrl || "").trim();
+ const baseUrl = (useSettingsStore.getState().cleanupCloudBaseUrl || "").trim();
if (!baseUrl) {
setTestResult(
t("promptStudio.test.baseUrlMissing", {
provider:
- reasoningProvider === "custom"
+ cleanupProvider === "custom"
? t("promptStudio.test.customEndpoint")
: providerConfig.label,
})
@@ -179,20 +148,15 @@ export default function PromptStudio({ className = "" }: PromptStudioProps) {
}
}
- const modelToUse = isCloudMode ? effectiveModel || "auto" : reasoningModel;
-
- const currentCustomPrompt = localStorage.getItem("customUnifiedPrompt");
- localStorage.setItem("customUnifiedPrompt", JSON.stringify(editedPrompt));
+ const modelToUse = isCloudMode ? cleanupModel || "auto" : cleanupModel;
+ const previous = customPrompt;
+ setCustomPrompt(kind, editedPrompt);
try {
const result = await ReasoningService.processText(testText, modelToUse, agentName, {});
setTestResult(result);
} finally {
- if (currentCustomPrompt) {
- localStorage.setItem("customUnifiedPrompt", currentCustomPrompt);
- } else {
- localStorage.removeItem("customUnifiedPrompt");
- }
+ setCustomPrompt(kind, previous);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
@@ -204,7 +168,8 @@ export default function PromptStudio({ className = "" }: PromptStudioProps) {
};
const isAgentAddressed = testText.toLowerCase().includes(agentName.toLowerCase());
- const isCustomPrompt = getCurrentPrompt() !== UNIFIED_SYSTEM_PROMPT;
+ const isCustomPrompt = customPrompt.length > 0;
+ const currentPrompt = customPrompt || defaultPrompt;
const tabs = [
{ id: "current" as const, label: t("promptStudio.tabs.view"), icon: Eye },
@@ -285,7 +250,7 @@ export default function PromptStudio({ className = "" }: PromptStudioProps) {
)}