From d13a16111cdcf488ade97f243dd43e8ee907969a Mon Sep 17 00:00:00 2001 From: Shirone Date: Sat, 3 Jan 2026 02:56:08 +0100 Subject: [PATCH] feat: enhance suggestion generation with model and thinking level overrides - Updated the generateSuggestions function to accept model and thinking level overrides, allowing for more flexible suggestion generation. - Modified the API client and UI components to support passing these new parameters, improving user control over the suggestion process. - Introduced a new phase model for AI Suggestions in settings, enhancing the overall functionality and user experience. --- .../suggestions/generate-suggestions.ts | 35 ++++++++++++++----- .../src/routes/suggestions/routes/generate.ts | 20 +++++++++-- .../dialogs/feature-suggestions-dialog.tsx | 25 +++++++++++-- apps/ui/src/lib/electron.ts | 18 ++++++++-- apps/ui/src/lib/http-api-client.ts | 9 +++-- libs/types/src/settings.ts | 3 ++ 6 files changed, 93 insertions(+), 17 deletions(-) diff --git a/apps/server/src/routes/suggestions/generate-suggestions.ts b/apps/server/src/routes/suggestions/generate-suggestions.ts index f06488e6..19b73838 100644 --- a/apps/server/src/routes/suggestions/generate-suggestions.ts +++ b/apps/server/src/routes/suggestions/generate-suggestions.ts @@ -1,14 +1,14 @@ /** * Business logic for generating suggestions * - * Model is configurable via phaseModels.enhancementModel in settings - * (Feature Enhancement in the UI). Supports both Claude and Cursor models. + * Model is configurable via phaseModels.suggestionsModel in settings + * (AI Suggestions in the UI). Supports both Claude and Cursor models. */ import { query } from '@anthropic-ai/claude-agent-sdk'; import type { EventEmitter } from '../../lib/events.js'; import { createLogger } from '@automaker/utils'; -import { DEFAULT_PHASE_MODELS, isCursorModel } from '@automaker/types'; +import { DEFAULT_PHASE_MODELS, isCursorModel, type ThinkingLevel } from '@automaker/types'; import { resolvePhaseModel } from '@automaker/model-resolver'; import { createSuggestionsOptions } from '../../lib/sdk-options.js'; import { extractJsonWithArray } from '../../lib/json-extractor.js'; @@ -135,7 +135,9 @@ export async function generateSuggestions( suggestionType: string, events: EventEmitter, abortController: AbortController, - settingsService?: SettingsService + settingsService?: SettingsService, + modelOverride?: string, + thinkingLevelOverride?: ThinkingLevel ): Promise { const typePrompts: Record = { features: 'Analyze this project and suggest new features that would add value.', @@ -171,11 +173,28 @@ The response will be automatically formatted as structured JSON.`; '[Suggestions]' ); - // Get model from phase settings (Feature Enhancement = enhancementModel) + // Get model from phase settings (AI Suggestions = suggestionsModel) + // Use override if provided, otherwise fall back to settings const settings = await settingsService?.getGlobalSettings(); - const phaseModelEntry = - settings?.phaseModels?.enhancementModel || DEFAULT_PHASE_MODELS.enhancementModel; - const { model, thinkingLevel } = resolvePhaseModel(phaseModelEntry); + let model: string; + let thinkingLevel: ThinkingLevel | undefined; + + if (modelOverride) { + // Use explicit override - resolve the model string + const resolved = resolvePhaseModel({ + model: modelOverride, + thinkingLevel: thinkingLevelOverride, + }); + model = resolved.model; + thinkingLevel = resolved.thinkingLevel; + } else { + // Use settings-based model + const phaseModelEntry = + settings?.phaseModels?.suggestionsModel || DEFAULT_PHASE_MODELS.suggestionsModel; + const resolved = resolvePhaseModel(phaseModelEntry); + model = resolved.model; + thinkingLevel = resolved.thinkingLevel; + } logger.info('[Suggestions] Using model:', model); diff --git a/apps/server/src/routes/suggestions/routes/generate.ts b/apps/server/src/routes/suggestions/routes/generate.ts index da57ed76..6ce2427b 100644 --- a/apps/server/src/routes/suggestions/routes/generate.ts +++ b/apps/server/src/routes/suggestions/routes/generate.ts @@ -5,6 +5,7 @@ import type { Request, Response } from 'express'; import type { EventEmitter } from '../../../lib/events.js'; import { createLogger } from '@automaker/utils'; +import type { ThinkingLevel } from '@automaker/types'; import { getSuggestionsStatus, setRunningState, getErrorMessage, logError } from '../common.js'; import { generateSuggestions } from '../generate-suggestions.js'; import type { SettingsService } from '../../../services/settings-service.js'; @@ -14,9 +15,16 @@ const logger = createLogger('Suggestions'); export function createGenerateHandler(events: EventEmitter, settingsService?: SettingsService) { return async (req: Request, res: Response): Promise => { try { - const { projectPath, suggestionType = 'features' } = req.body as { + const { + projectPath, + suggestionType = 'features', + model, + thinkingLevel, + } = req.body as { projectPath: string; suggestionType?: string; + model?: string; + thinkingLevel?: ThinkingLevel; }; if (!projectPath) { @@ -38,7 +46,15 @@ export function createGenerateHandler(events: EventEmitter, settingsService?: Se setRunningState(true, abortController); // Start generation in background - generateSuggestions(projectPath, suggestionType, events, abortController, settingsService) + generateSuggestions( + projectPath, + suggestionType, + events, + abortController, + settingsService, + model, + thinkingLevel + ) .catch((error) => { logError(error, 'Generate suggestions failed (background)'); events.emit('suggestions:event', { diff --git a/apps/ui/src/components/views/board-view/dialogs/feature-suggestions-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/feature-suggestions-dialog.tsx index f52b7958..8fdca7e6 100644 --- a/apps/ui/src/components/views/board-view/dialogs/feature-suggestions-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/feature-suggestions-dialog.tsx @@ -34,6 +34,8 @@ import { import { useAppStore, Feature } from '@/store/app-store'; import { toast } from 'sonner'; import { LogViewer } from '@/components/ui/log-viewer'; +import { useModelOverride } from '@/components/shared/use-model-override'; +import { ModelOverrideTrigger } from '@/components/shared/model-override-trigger'; const logger = createLogger('FeatureSuggestions'); @@ -104,6 +106,11 @@ export function FeatureSuggestionsDialog({ const { features, setFeatures } = useAppStore(); + // Model override for suggestions + const { effectiveModelEntry, isOverridden, setOverride } = useModelOverride({ + phase: 'suggestionsModel', + }); + // Initialize selectedIds when suggestions change useEffect(() => { if (suggestions.length > 0 && selectedIds.size === 0) { @@ -173,7 +180,13 @@ export function FeatureSuggestionsDialog({ setCurrentSuggestionType(suggestionType); try { - const result = await api.suggestions.generate(projectPath, suggestionType); + // Pass model and thinkingLevel from the effective model entry + const result = await api.suggestions.generate( + projectPath, + suggestionType, + effectiveModelEntry.model, + effectiveModelEntry.thinkingLevel + ); if (!result.success) { toast.error(result.error || 'Failed to start generation'); setIsGenerating(false); @@ -184,7 +197,7 @@ export function FeatureSuggestionsDialog({ setIsGenerating(false); } }, - [projectPath, setIsGenerating, setSuggestions] + [projectPath, setIsGenerating, setSuggestions, effectiveModelEntry] ); // Stop generating @@ -330,6 +343,14 @@ export function FeatureSuggestionsDialog({ AI Suggestions )} + {currentConfig diff --git a/apps/ui/src/lib/electron.ts b/apps/ui/src/lib/electron.ts index 6e0b3f41..40e03a82 100644 --- a/apps/ui/src/lib/electron.ts +++ b/apps/ui/src/lib/electron.ts @@ -14,6 +14,7 @@ import type { ModelAlias, GitHubComment, IssueCommentsResult, + ThinkingLevel, } from '@automaker/types'; import { getJSON, setJSON, removeItem } from './storage'; @@ -282,7 +283,9 @@ export type SuggestionType = 'features' | 'refactoring' | 'security' | 'performa export interface SuggestionsAPI { generate: ( projectPath: string, - suggestionType?: SuggestionType + suggestionType?: SuggestionType, + model?: string, + thinkingLevel?: ThinkingLevel ) => Promise<{ success: boolean; error?: string }>; stop: () => Promise<{ success: boolean; error?: string }>; status: () => Promise<{ @@ -2065,7 +2068,12 @@ let mockSuggestionsTimeout: NodeJS.Timeout | null = null; function createMockSuggestionsAPI(): SuggestionsAPI { return { - generate: async (projectPath: string, suggestionType: SuggestionType = 'features') => { + generate: async ( + projectPath: string, + suggestionType: SuggestionType = 'features', + model?: string, + thinkingLevel?: ThinkingLevel + ) => { if (mockSuggestionsRunning) { return { success: false, @@ -2074,7 +2082,11 @@ function createMockSuggestionsAPI(): SuggestionsAPI { } mockSuggestionsRunning = true; - logger.info(`Mock generating ${suggestionType} suggestions for: ${projectPath}`); + logger.info( + `Mock generating ${suggestionType} suggestions for: ${projectPath}` + + (model ? ` with model: ${model}` : '') + + (thinkingLevel ? ` thinkingLevel: ${thinkingLevel}` : '') + ); // Simulate async suggestion generation simulateSuggestionsGeneration(suggestionType); diff --git a/apps/ui/src/lib/http-api-client.ts b/apps/ui/src/lib/http-api-client.ts index 732274fa..bf00e122 100644 --- a/apps/ui/src/lib/http-api-client.ts +++ b/apps/ui/src/lib/http-api-client.ts @@ -1305,8 +1305,13 @@ export class HttpApiClient implements ElectronAPI { // Suggestions API suggestions: SuggestionsAPI = { - generate: (projectPath: string, suggestionType?: SuggestionType) => - this.post('/api/suggestions/generate', { projectPath, suggestionType }), + generate: ( + projectPath: string, + suggestionType?: SuggestionType, + model?: string, + thinkingLevel?: string + ) => + this.post('/api/suggestions/generate', { projectPath, suggestionType, model, thinkingLevel }), stop: () => this.post('/api/suggestions/stop'), status: () => this.get('/api/suggestions/status'), onEvent: (callback: (event: SuggestionsEvent) => void) => { diff --git a/libs/types/src/settings.ts b/libs/types/src/settings.ts index 55375809..7fd25092 100644 --- a/libs/types/src/settings.ts +++ b/libs/types/src/settings.ts @@ -139,6 +139,8 @@ export interface PhaseModelConfig { backlogPlanningModel: PhaseModelEntry; /** Model for analyzing project structure */ projectAnalysisModel: PhaseModelEntry; + /** Model for AI suggestions (feature, refactoring, security, performance) */ + suggestionsModel: PhaseModelEntry; } /** Keys of PhaseModelConfig for type-safe access */ @@ -608,6 +610,7 @@ export const DEFAULT_PHASE_MODELS: PhaseModelConfig = { featureGenerationModel: { model: 'sonnet' }, backlogPlanningModel: { model: 'sonnet' }, projectAnalysisModel: { model: 'sonnet' }, + suggestionsModel: { model: 'sonnet' }, }; /** Current version of the global settings schema */