diff --git a/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx b/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx index b2d1c3fd..4af506a0 100644 --- a/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx @@ -12,9 +12,6 @@ import { useUIStore } from '@/stores/useUIStore'; import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry'; import { getModifierLabel, cn } from '@/lib/utils'; -const UTILITY_PROVIDER_ID = 'zen'; -const UTILITY_PREFERRED_MODEL_ID = 'big-pickle'; - const getDisplayModel = ( storedModel: string | undefined ): { providerId: string; modelId: string } => { @@ -28,45 +25,6 @@ const getDisplayModel = ( return { providerId: '', modelId: '' }; }; -const getUtilityDisplayModel = ( - storedGitProviderId: string | undefined, - storedGitModelId: string | undefined, - zenModel: string | undefined, - providers: Array<{ id: string; models: Array<{ id: string }> }> -): { providerId: string; modelId: string } => { - if (storedGitProviderId && storedGitModelId) { - const provider = providers.find((p) => p.id === storedGitProviderId); - if (provider?.models.some((m) => m.id === storedGitModelId)) { - return { providerId: storedGitProviderId, modelId: storedGitModelId }; - } - } - - const utilityProvider = providers.find((p) => p.id === UTILITY_PROVIDER_ID); - if (zenModel && utilityProvider?.models.some((m) => m.id === zenModel)) { - return { providerId: UTILITY_PROVIDER_ID, modelId: zenModel }; - } - - const preferredUtilityModel = utilityProvider?.models.find((m) => m.id === UTILITY_PREFERRED_MODEL_ID); - if (preferredUtilityModel) { - return { providerId: UTILITY_PROVIDER_ID, modelId: preferredUtilityModel.id }; - } - - if (utilityProvider?.models.length) { - const randomIndex = Math.floor(Math.random() * utilityProvider.models.length); - const randomModel = utilityProvider.models[randomIndex]; - if (randomModel?.id) { - return { providerId: UTILITY_PROVIDER_ID, modelId: randomModel.id }; - } - } - - const firstProvider = providers[0]; - if (firstProvider?.models[0]) { - return { providerId: firstProvider.id, modelId: firstProvider.models[0].id }; - } - - return { providerId: '', modelId: '' }; -}; - export const DefaultsSettings: React.FC = () => { const setProvider = useConfigStore((state) => state.setProvider); const setModel = useConfigStore((state) => state.setModel); @@ -77,12 +35,6 @@ export const DefaultsSettings: React.FC = () => { const setSettingsDefaultAgent = useConfigStore((state) => state.setSettingsDefaultAgent); const settingsAutoCreateWorktree = useConfigStore((state) => state.settingsAutoCreateWorktree); const setSettingsAutoCreateWorktree = useConfigStore((state) => state.setSettingsAutoCreateWorktree); - const settingsZenModel = useConfigStore((state) => state.settingsZenModel); - const setSettingsZenModel = useConfigStore((state) => state.setSettingsZenModel); - const settingsGitProviderId = useConfigStore((state) => state.settingsGitProviderId); - const settingsGitModelId = useConfigStore((state) => state.settingsGitModelId); - const setSettingsGitProviderId = useConfigStore((state) => state.setSettingsGitProviderId); - const setSettingsGitModelId = useConfigStore((state) => state.setSettingsGitModelId); const showDeletionDialog = useUIStore((state) => state.showDeletionDialog); const setShowDeletionDialog = useUIStore((state) => state.setShowDeletionDialog); const providers = useConfigStore((state) => state.providers); @@ -93,9 +45,6 @@ export const DefaultsSettings: React.FC = () => { const [isLoading, setIsLoading] = React.useState(true); const parsedModel = React.useMemo(() => getDisplayModel(defaultModel), [defaultModel]); - const utilityDisplayModel = React.useMemo(() => { - return getUtilityDisplayModel(settingsGitProviderId, settingsGitModelId, settingsZenModel, providers); - }, [settingsGitProviderId, settingsGitModelId, settingsZenModel, providers]); const isVSCode = React.useMemo(() => isVSCodeRuntime(), []); React.useEffect(() => { @@ -105,9 +54,6 @@ export const DefaultsSettings: React.FC = () => { defaultModel?: string; defaultVariant?: string; defaultAgent?: string; - zenModel?: string; - gitProviderId?: string; - gitModelId?: string; } | null = null; if (!data) { @@ -124,18 +70,6 @@ export const DefaultsSettings: React.FC = () => { ? ((settings as Record).defaultVariant as string) : undefined, defaultAgent: typeof settings.defaultAgent === 'string' ? settings.defaultAgent : undefined, - zenModel: - typeof (settings as Record).zenModel === 'string' - ? ((settings as Record).zenModel as string) - : undefined, - gitProviderId: - typeof (settings as Record).gitProviderId === 'string' - ? ((settings as Record).gitProviderId as string) - : undefined, - gitModelId: - typeof (settings as Record).gitModelId === 'string' - ? ((settings as Record).gitModelId as string) - : undefined, }; } } catch { @@ -167,25 +101,10 @@ export const DefaultsSettings: React.FC = () => { typeof data.defaultAgent === 'string' && data.defaultAgent.trim().length > 0 ? data.defaultAgent.trim() : undefined; - const zen = - typeof data.zenModel === 'string' && data.zenModel.trim().length > 0 - ? data.zenModel.trim() - : undefined; - const gitProviderId = - typeof data.gitProviderId === 'string' && data.gitProviderId.trim().length > 0 - ? data.gitProviderId.trim() - : undefined; - const gitModelId = - typeof data.gitModelId === 'string' && data.gitModelId.trim().length > 0 - ? data.gitModelId.trim() - : undefined; if (model !== undefined) setDefaultModel(model); if (variant !== undefined) setDefaultVariant(variant); if (agent !== undefined) setDefaultAgent(agent); - if (zen !== undefined) setSettingsZenModel(zen); - if (gitProviderId !== undefined) setSettingsGitProviderId(gitProviderId); - if (gitModelId !== undefined) setSettingsGitModelId(gitModelId); } } catch (error) { console.warn('Failed to load defaults settings:', error); @@ -194,7 +113,7 @@ export const DefaultsSettings: React.FC = () => { } }; loadSettings(); - }, [setSettingsGitModelId, setSettingsGitProviderId, setSettingsZenModel]); + }, []); const handleModelChange = React.useCallback( async (providerId: string, modelId: string) => { @@ -303,26 +222,6 @@ export const DefaultsSettings: React.FC = () => { [setSettingsAutoCreateWorktree] ); - const handleUtilityModelChange = React.useCallback( - async (providerId: string, modelId: string) => { - setSettingsGitProviderId(providerId); - setSettingsGitModelId(modelId); - if (providerId === UTILITY_PROVIDER_ID) { - setSettingsZenModel(modelId); - } - try { - await updateDesktopSettings({ - gitProviderId: providerId, - gitModelId: modelId, - ...(providerId === UTILITY_PROVIDER_ID ? { zenModel: modelId } : {}), - }); - } catch (error) { - console.warn('Failed to save utility model setting:', error); - } - }, - [setSettingsGitModelId, setSettingsGitProviderId, setSettingsZenModel] - ); - if (isLoading) { return null; } @@ -393,29 +292,6 @@ export const DefaultsSettings: React.FC = () => { -
-
-
- Utility Model - - - - - - The model used for lightweight background tasks like commit messages, PR descriptions, and summarization. - - -
-
-
- -
-
-
{ const setSummaryLength = useUIStore(state => state.setSummaryLength); const maxLastMessageLength = useUIStore(state => state.maxLastMessageLength); const setMaxLastMessageLength = useUIStore(state => state.setMaxLastMessageLength); + const providers = useConfigStore((state) => state.providers); + const settingsZenModel = useConfigStore((state) => state.settingsZenModel); + const setSettingsZenModel = useConfigStore((state) => state.setSettingsZenModel); const [notificationPermission, setNotificationPermission] = React.useState('default'); const [pushSupported, setPushSupported] = React.useState(false); const [pushSubscribed, setPushSubscribed] = React.useState(false); const [pushBusy, setPushBusy] = React.useState(false); + const [fetchedZenModels, setFetchedZenModels] = React.useState>([]); + + const providerZenModels = React.useMemo(() => { + const zenProvider = providers.find((provider) => provider.id === UTILITY_PROVIDER_ID); + const models = Array.isArray(zenProvider?.models) ? zenProvider.models : []; + return models + .map((model: Record) => { + const id = typeof model.id === 'string' ? model.id.trim() : ''; + if (!id) { + return null; + } + const name = typeof model.name === 'string' && model.name.trim().length > 0 ? model.name.trim() : id; + return { id, name }; + }) + .filter((model): model is { id: string; name: string } => model !== null); + }, [providers]); + + React.useEffect(() => { + if (providerZenModels.length > 0) { + setFetchedZenModels([]); + return; + } + + const controller = new AbortController(); + void fetch('/api/zen/models', { + method: 'GET', + headers: { Accept: 'application/json' }, + signal: controller.signal, + }) + .then(async (response) => { + if (!response.ok) { + return [] as Array<{ id: string; name: string }>; + } + const payload = await response.json().catch(() => ({})); + const models = Array.isArray(payload?.models) ? payload.models : []; + return models + .map((entry: unknown) => { + const id = typeof (entry as { id?: unknown })?.id === 'string' + ? (entry as { id: string }).id.trim() + : ''; + if (!id) { + return null; + } + return { id, name: id }; + }) + .filter((entry: { id: string; name: string } | null): entry is { id: string; name: string } => entry !== null); + }) + .then((models) => { + setFetchedZenModels(models); + }) + .catch((error) => { + if (error?.name !== 'AbortError') { + console.warn('Failed to load zen utility models:', error); + } + }); + + return () => { + controller.abort(); + }; + }, [providerZenModels]); + + const utilityModelOptions = React.useMemo(() => { + return providerZenModels.length > 0 ? providerZenModels : fetchedZenModels; + }, [fetchedZenModels, providerZenModels]); + + const utilitySelectedModelId = React.useMemo(() => { + if (settingsZenModel && utilityModelOptions.some((model) => model.id === settingsZenModel)) { + return settingsZenModel; + } + if (utilityModelOptions.some((model) => model.id === UTILITY_PREFERRED_MODEL_ID)) { + return UTILITY_PREFERRED_MODEL_ID; + } + return utilityModelOptions[0]?.id ?? ''; + }, [settingsZenModel, utilityModelOptions]); + + const handleUtilityModelChange = React.useCallback( + async (value: string) => { + const modelId = value === UTILITY_NOT_SELECTED_VALUE ? undefined : value; + setSettingsZenModel(modelId); + try { + await updateDesktopSettings({ + zenModel: modelId ?? '', + gitProviderId: '', + gitModelId: '', + }); + } catch (error) { + console.warn('Failed to save utility model setting:', error); + } + }, + [setSettingsZenModel] + ); React.useEffect(() => { if (!isBrowser) { @@ -648,6 +750,40 @@ export const NotificationSettings: React.FC = () => { Summarize Last Message
+
+
+
+ Summarization Model + + + + + + Used for notification and voice summaries. + + +
+
+
+ +
+
+ {summarizeLastMessage ? ( <>
diff --git a/packages/ui/src/lib/gitApi.ts b/packages/ui/src/lib/gitApi.ts index 5cde04b2..6e33e74f 100644 --- a/packages/ui/src/lib/gitApi.ts +++ b/packages/ui/src/lib/gitApi.ts @@ -116,7 +116,7 @@ export async function generateCommitMessage( const generationSession = resolveSessionGenerationContext(); if (!generationSession) { - throw new Error('Select an active session for generation'); + throw new Error('Select existing session for generation'); } console.info('[git-generation][browser] request', { @@ -205,7 +205,7 @@ export async function generatePullRequestDescription( const startedAt = Date.now(); const generationSession = resolveSessionGenerationContext(); if (!generationSession) { - throw new Error('Select an active session for generation'); + throw new Error('Select existing session for generation'); } const commitLog = await getGitLog(directory, { diff --git a/packages/ui/src/stores/useConfigStore.ts b/packages/ui/src/stores/useConfigStore.ts index e5c8cfeb..9d78ccde 100644 --- a/packages/ui/src/stores/useConfigStore.ts +++ b/packages/ui/src/stores/useConfigStore.ts @@ -28,8 +28,6 @@ interface OpenChamberDefaults { autoCreateWorktree?: boolean; gitmojiEnabled?: boolean; zenModel?: string; - gitProviderId?: string; - gitModelId?: string; } const fetchOpenChamberDefaults = async (): Promise => { @@ -46,8 +44,6 @@ const fetchOpenChamberDefaults = async (): Promise => { const defaultAgent = typeof data?.defaultAgent === 'string' ? data.defaultAgent.trim() : ''; const gitmojiEnabled = typeof data?.gitmojiEnabled === 'boolean' ? data.gitmojiEnabled : undefined; const zenModel = typeof data?.zenModel === 'string' ? data.zenModel.trim() : ''; - const gitProviderId = typeof data?.gitProviderId === 'string' ? data.gitProviderId.trim() : ''; - const gitModelId = typeof data?.gitModelId === 'string' ? data.gitModelId.trim() : ''; return { defaultModel: defaultModel.length > 0 ? defaultModel : undefined, @@ -56,8 +52,6 @@ const fetchOpenChamberDefaults = async (): Promise => { autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined, gitmojiEnabled, zenModel: zenModel.length > 0 ? zenModel : undefined, - gitProviderId: gitProviderId.length > 0 ? gitProviderId : undefined, - gitModelId: gitModelId.length > 0 ? gitModelId : undefined, }; } } catch { @@ -79,8 +73,6 @@ const fetchOpenChamberDefaults = async (): Promise => { const defaultAgent = typeof data?.defaultAgent === 'string' ? data.defaultAgent.trim() : ''; const gitmojiEnabled = typeof data?.gitmojiEnabled === 'boolean' ? data.gitmojiEnabled : undefined; const zenModel = typeof data?.zenModel === 'string' ? data.zenModel.trim() : ''; - const gitProviderId = typeof data?.gitProviderId === 'string' ? data.gitProviderId.trim() : ''; - const gitModelId = typeof data?.gitModelId === 'string' ? data.gitModelId.trim() : ''; return { defaultModel: defaultModel.length > 0 ? defaultModel : undefined, @@ -89,8 +81,6 @@ const fetchOpenChamberDefaults = async (): Promise => { autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined, gitmojiEnabled, zenModel: zenModel.length > 0 ? zenModel : undefined, - gitProviderId: gitProviderId.length > 0 ? gitProviderId : undefined, - gitModelId: gitModelId.length > 0 ? gitModelId : undefined, }; } catch { return {}; @@ -139,17 +129,11 @@ const hasProviderModel = ( const resolveGitGenerationModelSelection = ({ providers, - settingsGitProviderId, - settingsGitModelId, settingsZenModel, }: { providers: ProviderWithModelList[]; - settingsGitProviderId?: string; - settingsGitModelId?: string; settingsZenModel?: string; }): GitModelSelection | null => { - const gitProviderId = normalizeOptionalString(settingsGitProviderId); - const gitModelId = normalizeOptionalString(settingsGitModelId); const zenModel = normalizeOptionalString(settingsZenModel); if (!Array.isArray(providers) || providers.length === 0) { @@ -159,10 +143,6 @@ const resolveGitGenerationModelSelection = ({ return null; } - if (gitProviderId && gitModelId && hasProviderModel(providers, gitProviderId, gitModelId)) { - return { providerId: gitProviderId, modelId: gitModelId }; - } - if (zenModel && hasProviderModel(providers, GIT_UTILITY_PROVIDER_ID, zenModel)) { return { providerId: GIT_UTILITY_PROVIDER_ID, modelId: zenModel }; } @@ -180,12 +160,6 @@ const resolveGitGenerationModelSelection = ({ } } - const firstProvider = providers.find((provider) => provider.models.length > 0); - const firstModelId = normalizeOptionalString(firstProvider?.models[0]?.id); - if (firstProvider?.id && firstModelId) { - return { providerId: firstProvider.id, modelId: firstModelId }; - } - return null; }; @@ -458,8 +432,6 @@ interface ConfigStore { settingsAutoCreateWorktree: boolean; settingsGitmojiEnabled: boolean; settingsZenModel: string | undefined; - settingsGitProviderId: string | undefined; - settingsGitModelId: string | undefined; // Voice provider preference ('browser', 'openai', or 'say' for macOS) voiceProvider: 'browser' | 'openai' | 'say'; setVoiceProvider: (provider: 'browser' | 'openai' | 'say') => void; @@ -509,8 +481,6 @@ interface ConfigStore { setSettingsAutoCreateWorktree: (enabled: boolean) => void; setSettingsGitmojiEnabled: (enabled: boolean) => void; setSettingsZenModel: (model: string | undefined) => void; - setSettingsGitProviderId: (providerId: string | undefined) => void; - setSettingsGitModelId: (modelId: string | undefined) => void; getResolvedGitGenerationModel: () => { providerId: string; modelId: string } | null; saveAgentModelSelection: (agentName: string, providerId: string, modelId: string) => void; getAgentModelSelection: (agentName: string) => { providerId: string; modelId: string } | null; @@ -557,8 +527,6 @@ export const useConfigStore = create()( settingsAutoCreateWorktree: false, settingsGitmojiEnabled: false, settingsZenModel: undefined, - settingsGitProviderId: undefined, - settingsGitModelId: undefined, // Voice provider preference - load from localStorage or default to 'browser' voiceProvider: (() => { if (typeof window !== 'undefined') { @@ -1100,35 +1068,23 @@ export const useConfigStore = create()( ? get().providers : (get().directoryScoped[directoryKey]?.providers ?? []); - const existingGitProviderId = normalizeOptionalString(get().settingsGitProviderId); - const existingGitModelId = normalizeOptionalString(get().settingsGitModelId); const existingZenModel = normalizeOptionalString(get().settingsZenModel); - const defaultGitProviderId = normalizeOptionalString(openChamberDefaults.gitProviderId); - const defaultGitModelId = normalizeOptionalString(openChamberDefaults.gitModelId); const defaultZenModel = normalizeOptionalString(openChamberDefaults.zenModel); const resolvedExistingGitSelection = resolveGitGenerationModelSelection({ providers, - settingsGitProviderId: existingGitProviderId, - settingsGitModelId: existingGitModelId, settingsZenModel: existingZenModel, }); const resolvedDefaultGitSelection = resolveGitGenerationModelSelection({ providers, - settingsGitProviderId: defaultGitProviderId, - settingsGitModelId: defaultGitModelId, settingsZenModel: defaultZenModel, }); const resolvedGitSelection = resolvedExistingGitSelection || resolvedDefaultGitSelection; - const resolvedGitProviderId = resolvedGitSelection?.providerId; const resolvedGitModelId = resolvedGitSelection?.modelId; - const resolvedZenModel = - resolvedGitProviderId === GIT_UTILITY_PROVIDER_ID && resolvedGitModelId - ? resolvedGitModelId - : (defaultZenModel || existingZenModel); + const resolvedZenModel = resolvedGitModelId || defaultZenModel || existingZenModel; set((state) => { const baseSnapshot: DirectoryScopedConfig = state.directoryScoped[directoryKey] ?? { @@ -1155,8 +1111,6 @@ export const useConfigStore = create()( settingsAutoCreateWorktree: openChamberDefaults.autoCreateWorktree ?? false, settingsGitmojiEnabled: openChamberDefaults.gitmojiEnabled ?? false, settingsZenModel: resolvedZenModel, - settingsGitProviderId: resolvedGitProviderId, - settingsGitModelId: resolvedGitModelId, directoryScoped: { ...state.directoryScoped, [directoryKey]: nextSnapshot, @@ -1170,33 +1124,16 @@ export const useConfigStore = create()( return nextState; }); - const shouldPersistResolvedGitSelection = - !!resolvedGitProviderId && - !!resolvedGitModelId && - ( - defaultGitProviderId !== resolvedGitProviderId || - defaultGitModelId !== resolvedGitModelId || - ( - resolvedGitProviderId === GIT_UTILITY_PROVIDER_ID && - resolvedZenModel !== defaultZenModel - ) - ); + const shouldPersistResolvedZenModel = + !!resolvedZenModel && + resolvedZenModel !== defaultZenModel; - if (shouldPersistResolvedGitSelection && resolvedGitProviderId && resolvedGitModelId) { - const gitSettingsUpdate: { - gitProviderId: string; - gitModelId: string; - zenModel?: string; - } = { - gitProviderId: resolvedGitProviderId, - gitModelId: resolvedGitModelId, - }; - - if (resolvedGitProviderId === GIT_UTILITY_PROVIDER_ID && resolvedZenModel) { - gitSettingsUpdate.zenModel = resolvedZenModel; - } - - updateDesktopSettings(gitSettingsUpdate).catch(() => { + if (shouldPersistResolvedZenModel && resolvedZenModel) { + updateDesktopSettings({ + zenModel: resolvedZenModel, + gitProviderId: '', + gitModelId: '', + }).catch(() => { // Ignore errors - best effort cleanup }); } @@ -1616,20 +1553,10 @@ export const useConfigStore = create()( set({ settingsZenModel: model }); }, - setSettingsGitProviderId: (providerId: string | undefined) => { - set({ settingsGitProviderId: providerId }); - }, - - setSettingsGitModelId: (modelId: string | undefined) => { - set({ settingsGitModelId: modelId }); - }, - getResolvedGitGenerationModel: () => { const state = get(); return resolveGitGenerationModelSelection({ providers: state.providers, - settingsGitProviderId: state.settingsGitProviderId, - settingsGitModelId: state.settingsGitModelId, settingsZenModel: state.settingsZenModel, }); }, @@ -1844,8 +1771,6 @@ export const useConfigStore = create()( settingsAutoCreateWorktree: state.settingsAutoCreateWorktree, settingsGitmojiEnabled: state.settingsGitmojiEnabled, settingsZenModel: state.settingsZenModel, - settingsGitProviderId: state.settingsGitProviderId, - settingsGitModelId: state.settingsGitModelId, speechRate: state.speechRate, speechPitch: state.speechPitch, speechVolume: state.speechVolume,