mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
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.
This commit is contained in:
@@ -1,14 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* Business logic for generating suggestions
|
* Business logic for generating suggestions
|
||||||
*
|
*
|
||||||
* Model is configurable via phaseModels.enhancementModel in settings
|
* Model is configurable via phaseModels.suggestionsModel in settings
|
||||||
* (Feature Enhancement in the UI). Supports both Claude and Cursor models.
|
* (AI Suggestions in the UI). Supports both Claude and Cursor models.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||||
import type { EventEmitter } from '../../lib/events.js';
|
import type { EventEmitter } from '../../lib/events.js';
|
||||||
import { createLogger } from '@automaker/utils';
|
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 { resolvePhaseModel } from '@automaker/model-resolver';
|
||||||
import { createSuggestionsOptions } from '../../lib/sdk-options.js';
|
import { createSuggestionsOptions } from '../../lib/sdk-options.js';
|
||||||
import { extractJsonWithArray } from '../../lib/json-extractor.js';
|
import { extractJsonWithArray } from '../../lib/json-extractor.js';
|
||||||
@@ -135,7 +135,9 @@ export async function generateSuggestions(
|
|||||||
suggestionType: string,
|
suggestionType: string,
|
||||||
events: EventEmitter,
|
events: EventEmitter,
|
||||||
abortController: AbortController,
|
abortController: AbortController,
|
||||||
settingsService?: SettingsService
|
settingsService?: SettingsService,
|
||||||
|
modelOverride?: string,
|
||||||
|
thinkingLevelOverride?: ThinkingLevel
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const typePrompts: Record<string, string> = {
|
const typePrompts: Record<string, string> = {
|
||||||
features: 'Analyze this project and suggest new features that would add value.',
|
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]'
|
'[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 settings = await settingsService?.getGlobalSettings();
|
||||||
const phaseModelEntry =
|
let model: string;
|
||||||
settings?.phaseModels?.enhancementModel || DEFAULT_PHASE_MODELS.enhancementModel;
|
let thinkingLevel: ThinkingLevel | undefined;
|
||||||
const { model, thinkingLevel } = resolvePhaseModel(phaseModelEntry);
|
|
||||||
|
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);
|
logger.info('[Suggestions] Using model:', model);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
import type { EventEmitter } from '../../../lib/events.js';
|
import type { EventEmitter } from '../../../lib/events.js';
|
||||||
import { createLogger } from '@automaker/utils';
|
import { createLogger } from '@automaker/utils';
|
||||||
|
import type { ThinkingLevel } from '@automaker/types';
|
||||||
import { getSuggestionsStatus, setRunningState, getErrorMessage, logError } from '../common.js';
|
import { getSuggestionsStatus, setRunningState, getErrorMessage, logError } from '../common.js';
|
||||||
import { generateSuggestions } from '../generate-suggestions.js';
|
import { generateSuggestions } from '../generate-suggestions.js';
|
||||||
import type { SettingsService } from '../../../services/settings-service.js';
|
import type { SettingsService } from '../../../services/settings-service.js';
|
||||||
@@ -14,9 +15,16 @@ const logger = createLogger('Suggestions');
|
|||||||
export function createGenerateHandler(events: EventEmitter, settingsService?: SettingsService) {
|
export function createGenerateHandler(events: EventEmitter, settingsService?: SettingsService) {
|
||||||
return async (req: Request, res: Response): Promise<void> => {
|
return async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { projectPath, suggestionType = 'features' } = req.body as {
|
const {
|
||||||
|
projectPath,
|
||||||
|
suggestionType = 'features',
|
||||||
|
model,
|
||||||
|
thinkingLevel,
|
||||||
|
} = req.body as {
|
||||||
projectPath: string;
|
projectPath: string;
|
||||||
suggestionType?: string;
|
suggestionType?: string;
|
||||||
|
model?: string;
|
||||||
|
thinkingLevel?: ThinkingLevel;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!projectPath) {
|
if (!projectPath) {
|
||||||
@@ -38,7 +46,15 @@ export function createGenerateHandler(events: EventEmitter, settingsService?: Se
|
|||||||
setRunningState(true, abortController);
|
setRunningState(true, abortController);
|
||||||
|
|
||||||
// Start generation in background
|
// Start generation in background
|
||||||
generateSuggestions(projectPath, suggestionType, events, abortController, settingsService)
|
generateSuggestions(
|
||||||
|
projectPath,
|
||||||
|
suggestionType,
|
||||||
|
events,
|
||||||
|
abortController,
|
||||||
|
settingsService,
|
||||||
|
model,
|
||||||
|
thinkingLevel
|
||||||
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logError(error, 'Generate suggestions failed (background)');
|
logError(error, 'Generate suggestions failed (background)');
|
||||||
events.emit('suggestions:event', {
|
events.emit('suggestions:event', {
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import {
|
|||||||
import { useAppStore, Feature } from '@/store/app-store';
|
import { useAppStore, Feature } from '@/store/app-store';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { LogViewer } from '@/components/ui/log-viewer';
|
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');
|
const logger = createLogger('FeatureSuggestions');
|
||||||
|
|
||||||
@@ -104,6 +106,11 @@ export function FeatureSuggestionsDialog({
|
|||||||
|
|
||||||
const { features, setFeatures } = useAppStore();
|
const { features, setFeatures } = useAppStore();
|
||||||
|
|
||||||
|
// Model override for suggestions
|
||||||
|
const { effectiveModelEntry, isOverridden, setOverride } = useModelOverride({
|
||||||
|
phase: 'suggestionsModel',
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize selectedIds when suggestions change
|
// Initialize selectedIds when suggestions change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (suggestions.length > 0 && selectedIds.size === 0) {
|
if (suggestions.length > 0 && selectedIds.size === 0) {
|
||||||
@@ -173,7 +180,13 @@ export function FeatureSuggestionsDialog({
|
|||||||
setCurrentSuggestionType(suggestionType);
|
setCurrentSuggestionType(suggestionType);
|
||||||
|
|
||||||
try {
|
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) {
|
if (!result.success) {
|
||||||
toast.error(result.error || 'Failed to start generation');
|
toast.error(result.error || 'Failed to start generation');
|
||||||
setIsGenerating(false);
|
setIsGenerating(false);
|
||||||
@@ -184,7 +197,7 @@ export function FeatureSuggestionsDialog({
|
|||||||
setIsGenerating(false);
|
setIsGenerating(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[projectPath, setIsGenerating, setSuggestions]
|
[projectPath, setIsGenerating, setSuggestions, effectiveModelEntry]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Stop generating
|
// Stop generating
|
||||||
@@ -330,6 +343,14 @@ export function FeatureSuggestionsDialog({
|
|||||||
AI Suggestions
|
AI Suggestions
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<ModelOverrideTrigger
|
||||||
|
currentModelEntry={effectiveModelEntry}
|
||||||
|
onModelChange={setOverride}
|
||||||
|
phase="suggestionsModel"
|
||||||
|
isOverridden={isOverridden}
|
||||||
|
size="sm"
|
||||||
|
variant="icon"
|
||||||
|
/>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{currentConfig
|
{currentConfig
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type {
|
|||||||
ModelAlias,
|
ModelAlias,
|
||||||
GitHubComment,
|
GitHubComment,
|
||||||
IssueCommentsResult,
|
IssueCommentsResult,
|
||||||
|
ThinkingLevel,
|
||||||
} from '@automaker/types';
|
} from '@automaker/types';
|
||||||
import { getJSON, setJSON, removeItem } from './storage';
|
import { getJSON, setJSON, removeItem } from './storage';
|
||||||
|
|
||||||
@@ -282,7 +283,9 @@ export type SuggestionType = 'features' | 'refactoring' | 'security' | 'performa
|
|||||||
export interface SuggestionsAPI {
|
export interface SuggestionsAPI {
|
||||||
generate: (
|
generate: (
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
suggestionType?: SuggestionType
|
suggestionType?: SuggestionType,
|
||||||
|
model?: string,
|
||||||
|
thinkingLevel?: ThinkingLevel
|
||||||
) => Promise<{ success: boolean; error?: string }>;
|
) => Promise<{ success: boolean; error?: string }>;
|
||||||
stop: () => Promise<{ success: boolean; error?: string }>;
|
stop: () => Promise<{ success: boolean; error?: string }>;
|
||||||
status: () => Promise<{
|
status: () => Promise<{
|
||||||
@@ -2065,7 +2068,12 @@ let mockSuggestionsTimeout: NodeJS.Timeout | null = null;
|
|||||||
|
|
||||||
function createMockSuggestionsAPI(): SuggestionsAPI {
|
function createMockSuggestionsAPI(): SuggestionsAPI {
|
||||||
return {
|
return {
|
||||||
generate: async (projectPath: string, suggestionType: SuggestionType = 'features') => {
|
generate: async (
|
||||||
|
projectPath: string,
|
||||||
|
suggestionType: SuggestionType = 'features',
|
||||||
|
model?: string,
|
||||||
|
thinkingLevel?: ThinkingLevel
|
||||||
|
) => {
|
||||||
if (mockSuggestionsRunning) {
|
if (mockSuggestionsRunning) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -2074,7 +2082,11 @@ function createMockSuggestionsAPI(): SuggestionsAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mockSuggestionsRunning = true;
|
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
|
// Simulate async suggestion generation
|
||||||
simulateSuggestionsGeneration(suggestionType);
|
simulateSuggestionsGeneration(suggestionType);
|
||||||
|
|||||||
@@ -1305,8 +1305,13 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
|
|
||||||
// Suggestions API
|
// Suggestions API
|
||||||
suggestions: SuggestionsAPI = {
|
suggestions: SuggestionsAPI = {
|
||||||
generate: (projectPath: string, suggestionType?: SuggestionType) =>
|
generate: (
|
||||||
this.post('/api/suggestions/generate', { projectPath, suggestionType }),
|
projectPath: string,
|
||||||
|
suggestionType?: SuggestionType,
|
||||||
|
model?: string,
|
||||||
|
thinkingLevel?: string
|
||||||
|
) =>
|
||||||
|
this.post('/api/suggestions/generate', { projectPath, suggestionType, model, thinkingLevel }),
|
||||||
stop: () => this.post('/api/suggestions/stop'),
|
stop: () => this.post('/api/suggestions/stop'),
|
||||||
status: () => this.get('/api/suggestions/status'),
|
status: () => this.get('/api/suggestions/status'),
|
||||||
onEvent: (callback: (event: SuggestionsEvent) => void) => {
|
onEvent: (callback: (event: SuggestionsEvent) => void) => {
|
||||||
|
|||||||
@@ -139,6 +139,8 @@ export interface PhaseModelConfig {
|
|||||||
backlogPlanningModel: PhaseModelEntry;
|
backlogPlanningModel: PhaseModelEntry;
|
||||||
/** Model for analyzing project structure */
|
/** Model for analyzing project structure */
|
||||||
projectAnalysisModel: PhaseModelEntry;
|
projectAnalysisModel: PhaseModelEntry;
|
||||||
|
/** Model for AI suggestions (feature, refactoring, security, performance) */
|
||||||
|
suggestionsModel: PhaseModelEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Keys of PhaseModelConfig for type-safe access */
|
/** Keys of PhaseModelConfig for type-safe access */
|
||||||
@@ -608,6 +610,7 @@ export const DEFAULT_PHASE_MODELS: PhaseModelConfig = {
|
|||||||
featureGenerationModel: { model: 'sonnet' },
|
featureGenerationModel: { model: 'sonnet' },
|
||||||
backlogPlanningModel: { model: 'sonnet' },
|
backlogPlanningModel: { model: 'sonnet' },
|
||||||
projectAnalysisModel: { model: 'sonnet' },
|
projectAnalysisModel: { model: 'sonnet' },
|
||||||
|
suggestionsModel: { model: 'sonnet' },
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Current version of the global settings schema */
|
/** Current version of the global settings schema */
|
||||||
|
|||||||
Reference in New Issue
Block a user