mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
feat: enhance ideation routes with event handling and new suggestion feature
- Updated the ideation routes to include an EventEmitter for better event management. - Added a new endpoint to handle adding suggestions to the board, ensuring consistent category mapping. - Modified existing routes to emit events for idea creation, update, and deletion, improving frontend notifications. - Refactored the convert and create idea handlers to utilize the new event system. - Removed static guided prompts data in favor of dynamic fetching from the backend API.
This commit is contained in:
@@ -218,7 +218,7 @@ app.use('/api/context', createContextRoutes(settingsService));
|
||||
app.use('/api/backlog-plan', createBacklogPlanRoutes(events, settingsService));
|
||||
app.use('/api/mcp', createMCPRoutes(mcpTestService));
|
||||
app.use('/api/pipeline', createPipelineRoutes(pipelineService));
|
||||
app.use('/api/ideation', createIdeationRoutes(ideationService, featureLoader));
|
||||
app.use('/api/ideation', createIdeationRoutes(events, ideationService, featureLoader));
|
||||
|
||||
// Create HTTP server
|
||||
const server = createServer(app);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import type { EventEmitter } from '../../lib/events.js';
|
||||
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||
import type { IdeationService } from '../../services/ideation-service.js';
|
||||
import type { FeatureLoader } from '../../services/feature-loader.js';
|
||||
@@ -19,10 +20,12 @@ import { createIdeasUpdateHandler } from './routes/ideas-update.js';
|
||||
import { createIdeasDeleteHandler } from './routes/ideas-delete.js';
|
||||
import { createAnalyzeHandler, createGetAnalysisHandler } from './routes/analyze.js';
|
||||
import { createConvertHandler } from './routes/convert.js';
|
||||
import { createAddSuggestionHandler } from './routes/add-suggestion.js';
|
||||
import { createPromptsHandler, createPromptsByCategoryHandler } from './routes/prompts.js';
|
||||
import { createSuggestionsGenerateHandler } from './routes/suggestions-generate.js';
|
||||
|
||||
export function createIdeationRoutes(
|
||||
events: EventEmitter,
|
||||
ideationService: IdeationService,
|
||||
featureLoader: FeatureLoader
|
||||
): Router {
|
||||
@@ -35,7 +38,7 @@ export function createIdeationRoutes(
|
||||
createSessionStartHandler(ideationService)
|
||||
);
|
||||
router.post('/session/message', createSessionMessageHandler(ideationService));
|
||||
router.post('/session/stop', createSessionStopHandler(ideationService));
|
||||
router.post('/session/stop', createSessionStopHandler(events, ideationService));
|
||||
router.post(
|
||||
'/session/get',
|
||||
validatePathParams('projectPath'),
|
||||
@@ -51,7 +54,7 @@ export function createIdeationRoutes(
|
||||
router.post(
|
||||
'/ideas/create',
|
||||
validatePathParams('projectPath'),
|
||||
createIdeasCreateHandler(ideationService)
|
||||
createIdeasCreateHandler(events, ideationService)
|
||||
);
|
||||
router.post(
|
||||
'/ideas/get',
|
||||
@@ -61,12 +64,12 @@ export function createIdeationRoutes(
|
||||
router.post(
|
||||
'/ideas/update',
|
||||
validatePathParams('projectPath'),
|
||||
createIdeasUpdateHandler(ideationService)
|
||||
createIdeasUpdateHandler(events, ideationService)
|
||||
);
|
||||
router.post(
|
||||
'/ideas/delete',
|
||||
validatePathParams('projectPath'),
|
||||
createIdeasDeleteHandler(ideationService)
|
||||
createIdeasDeleteHandler(events, ideationService)
|
||||
);
|
||||
|
||||
// Project analysis
|
||||
@@ -81,7 +84,14 @@ export function createIdeationRoutes(
|
||||
router.post(
|
||||
'/convert',
|
||||
validatePathParams('projectPath'),
|
||||
createConvertHandler(ideationService, featureLoader)
|
||||
createConvertHandler(events, ideationService, featureLoader)
|
||||
);
|
||||
|
||||
// Add suggestion to board as a feature
|
||||
router.post(
|
||||
'/add-suggestion',
|
||||
validatePathParams('projectPath'),
|
||||
createAddSuggestionHandler(ideationService, featureLoader)
|
||||
);
|
||||
|
||||
// Guided prompts (no validation needed - static data)
|
||||
|
||||
70
apps/server/src/routes/ideation/routes/add-suggestion.ts
Normal file
70
apps/server/src/routes/ideation/routes/add-suggestion.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* POST /add-suggestion - Add an analysis suggestion to the board as a feature
|
||||
*
|
||||
* This endpoint converts an AnalysisSuggestion to a Feature using the
|
||||
* IdeationService's mapIdeaCategoryToFeatureCategory for consistent category mapping.
|
||||
* This ensures a single source of truth for the conversion logic.
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import type { FeatureLoader } from '../../../services/feature-loader.js';
|
||||
import type { AnalysisSuggestion } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createAddSuggestionHandler(
|
||||
ideationService: IdeationService,
|
||||
featureLoader: FeatureLoader
|
||||
) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, suggestion } = req.body as {
|
||||
projectPath: string;
|
||||
suggestion: AnalysisSuggestion;
|
||||
};
|
||||
|
||||
if (!projectPath) {
|
||||
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!suggestion) {
|
||||
res.status(400).json({ success: false, error: 'suggestion is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!suggestion.title) {
|
||||
res.status(400).json({ success: false, error: 'suggestion.title is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!suggestion.category) {
|
||||
res.status(400).json({ success: false, error: 'suggestion.category is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Build description with rationale if provided
|
||||
const description = suggestion.rationale
|
||||
? `${suggestion.description}\n\n**Rationale:** ${suggestion.rationale}`
|
||||
: suggestion.description;
|
||||
|
||||
// Use the service's category mapping for consistency
|
||||
const featureCategory = ideationService.mapSuggestionCategoryToFeatureCategory(
|
||||
suggestion.category
|
||||
);
|
||||
|
||||
// Create the feature
|
||||
const feature = await featureLoader.create(projectPath, {
|
||||
title: suggestion.title,
|
||||
description,
|
||||
category: featureCategory,
|
||||
status: 'backlog',
|
||||
});
|
||||
|
||||
res.json({ success: true, featureId: feature.id });
|
||||
} catch (error) {
|
||||
logError(error, 'Add suggestion to board failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -3,12 +3,14 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import type { FeatureLoader } from '../../../services/feature-loader.js';
|
||||
import type { ConvertToFeatureOptions } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createConvertHandler(
|
||||
events: EventEmitter,
|
||||
ideationService: IdeationService,
|
||||
featureLoader: FeatureLoader
|
||||
) {
|
||||
@@ -49,8 +51,22 @@ export function createConvertHandler(
|
||||
// Delete the idea unless keepIdea is explicitly true
|
||||
if (!keepIdea) {
|
||||
await ideationService.deleteIdea(projectPath, ideaId);
|
||||
|
||||
// Emit idea deleted event
|
||||
events.emit('ideation:idea-deleted', {
|
||||
projectPath,
|
||||
ideaId,
|
||||
});
|
||||
}
|
||||
|
||||
// Emit idea converted event to notify frontend
|
||||
events.emit('ideation:idea-converted', {
|
||||
projectPath,
|
||||
ideaId,
|
||||
featureId: feature.id,
|
||||
keepIdea: !!keepIdea,
|
||||
});
|
||||
|
||||
// Return featureId as expected by the frontend API interface
|
||||
res.json({ success: true, featureId: feature.id });
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import type { CreateIdeaInput } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createIdeasCreateHandler(ideationService: IdeationService) {
|
||||
export function createIdeasCreateHandler(events: EventEmitter, ideationService: IdeationService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, idea } = req.body as {
|
||||
@@ -34,6 +35,13 @@ export function createIdeasCreateHandler(ideationService: IdeationService) {
|
||||
}
|
||||
|
||||
const created = await ideationService.createIdea(projectPath, idea);
|
||||
|
||||
// Emit idea created event for frontend notification
|
||||
events.emit('ideation:idea-created', {
|
||||
projectPath,
|
||||
idea: created,
|
||||
});
|
||||
|
||||
res.json({ success: true, idea: created });
|
||||
} catch (error) {
|
||||
logError(error, 'Create idea failed');
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createIdeasDeleteHandler(ideationService: IdeationService) {
|
||||
export function createIdeasDeleteHandler(events: EventEmitter, ideationService: IdeationService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, ideaId } = req.body as {
|
||||
@@ -25,6 +26,13 @@ export function createIdeasDeleteHandler(ideationService: IdeationService) {
|
||||
}
|
||||
|
||||
await ideationService.deleteIdea(projectPath, ideaId);
|
||||
|
||||
// Emit idea deleted event for frontend notification
|
||||
events.emit('ideation:idea-deleted', {
|
||||
projectPath,
|
||||
ideaId,
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logError(error, 'Delete idea failed');
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import type { UpdateIdeaInput } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createIdeasUpdateHandler(ideationService: IdeationService) {
|
||||
export function createIdeasUpdateHandler(events: EventEmitter, ideationService: IdeationService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, ideaId, updates } = req.body as {
|
||||
@@ -37,6 +38,13 @@ export function createIdeasUpdateHandler(ideationService: IdeationService) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit idea updated event for frontend notification
|
||||
events.emit('ideation:idea-updated', {
|
||||
projectPath,
|
||||
ideaId,
|
||||
idea,
|
||||
});
|
||||
|
||||
res.json({ success: true, idea });
|
||||
} catch (error) {
|
||||
logError(error, 'Update idea failed');
|
||||
|
||||
@@ -26,7 +26,7 @@ export function createPromptsByCategoryHandler(ideationService: IdeationService)
|
||||
try {
|
||||
const { category } = req.params as { category: string };
|
||||
|
||||
const validCategories: IdeaCategory[] = ['feature', 'ux-ui', 'dx', 'growth', 'technical'];
|
||||
const validCategories = ideationService.getPromptCategories().map((c) => c.id);
|
||||
if (!validCategories.includes(category as IdeaCategory)) {
|
||||
res.status(400).json({ success: false, error: 'Invalid category' });
|
||||
return;
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createSessionStopHandler(ideationService: IdeationService) {
|
||||
export function createSessionStopHandler(events: EventEmitter, ideationService: IdeationService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { sessionId } = req.body as { sessionId: string };
|
||||
const { sessionId, projectPath } = req.body as {
|
||||
sessionId: string;
|
||||
projectPath?: string;
|
||||
};
|
||||
|
||||
if (!sessionId) {
|
||||
res.status(400).json({ success: false, error: 'sessionId is required' });
|
||||
@@ -17,6 +21,15 @@ export function createSessionStopHandler(ideationService: IdeationService) {
|
||||
}
|
||||
|
||||
await ideationService.stopSession(sessionId);
|
||||
|
||||
// Emit session stopped event for frontend notification
|
||||
// Note: The service also emits 'ideation:session-ended' internally,
|
||||
// but we emit here as well for route-level consistency with other routes
|
||||
events.emit('ideation:session-ended', {
|
||||
sessionId,
|
||||
projectPath,
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logError(error, 'Stop session failed');
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import type { IdeationService } from '../../../services/ideation-service.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
const logger = createLogger('ideation:suggestions-generate');
|
||||
|
||||
@@ -45,10 +46,10 @@ export function createSuggestionsGenerateHandler(ideationService: IdeationServic
|
||||
suggestions,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate suggestions:', error);
|
||||
logError(error, 'Failed to generate suggestions');
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
error: getErrorMessage(error),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ import { ProviderFactory } from '../providers/provider-factory.js';
|
||||
import type { SettingsService } from './settings-service.js';
|
||||
import type { FeatureLoader } from './feature-loader.js';
|
||||
import { createChatOptions, validateWorkingDirectory } from '../lib/sdk-options.js';
|
||||
import { resolveModelString } from '@automaker/model-resolver';
|
||||
|
||||
const logger = createLogger('IdeationService');
|
||||
|
||||
@@ -200,20 +201,22 @@ export class IdeationService {
|
||||
existingWorkContext
|
||||
);
|
||||
|
||||
// Resolve model alias to canonical identifier
|
||||
const modelId = resolveModelString(options?.model ?? 'sonnet');
|
||||
|
||||
// Create SDK options
|
||||
const sdkOptions = createChatOptions({
|
||||
cwd: projectPath,
|
||||
model: options?.model || 'sonnet',
|
||||
model: modelId,
|
||||
systemPrompt,
|
||||
abortController: activeSession.abortController!,
|
||||
});
|
||||
|
||||
const effectiveModel = sdkOptions.model!;
|
||||
const provider = ProviderFactory.getProviderForModel(effectiveModel);
|
||||
const provider = ProviderFactory.getProviderForModel(modelId);
|
||||
|
||||
const executeOptions: ExecuteOptions = {
|
||||
prompt: message,
|
||||
model: effectiveModel,
|
||||
model: modelId,
|
||||
cwd: projectPath,
|
||||
systemPrompt: sdkOptions.systemPrompt,
|
||||
maxTurns: 1, // Single turn for ideation
|
||||
@@ -645,20 +648,22 @@ export class IdeationService {
|
||||
existingWorkContext
|
||||
);
|
||||
|
||||
// Resolve model alias to canonical identifier
|
||||
const modelId = resolveModelString('sonnet');
|
||||
|
||||
// Create SDK options
|
||||
const sdkOptions = createChatOptions({
|
||||
cwd: projectPath,
|
||||
model: 'sonnet',
|
||||
model: modelId,
|
||||
systemPrompt,
|
||||
abortController: new AbortController(),
|
||||
});
|
||||
|
||||
const effectiveModel = sdkOptions.model!;
|
||||
const provider = ProviderFactory.getProviderForModel(effectiveModel);
|
||||
const provider = ProviderFactory.getProviderForModel(modelId);
|
||||
|
||||
const executeOptions: ExecuteOptions = {
|
||||
prompt: prompt.prompt,
|
||||
model: effectiveModel,
|
||||
model: modelId,
|
||||
cwd: projectPath,
|
||||
systemPrompt: sdkOptions.systemPrompt,
|
||||
maxTurns: 1,
|
||||
@@ -892,6 +897,30 @@ ${contextSection}${existingWorkSection}`;
|
||||
icon: 'Cpu',
|
||||
description: 'Architecture and infrastructure',
|
||||
},
|
||||
{
|
||||
id: 'security',
|
||||
name: 'Security',
|
||||
icon: 'Shield',
|
||||
description: 'Security improvements and vulnerability fixes',
|
||||
},
|
||||
{
|
||||
id: 'performance',
|
||||
name: 'Performance',
|
||||
icon: 'Gauge',
|
||||
description: 'Performance optimization and speed improvements',
|
||||
},
|
||||
{
|
||||
id: 'accessibility',
|
||||
name: 'Accessibility',
|
||||
icon: 'Accessibility',
|
||||
description: 'Accessibility features and inclusive design',
|
||||
},
|
||||
{
|
||||
id: 'analytics',
|
||||
name: 'Analytics',
|
||||
icon: 'BarChart',
|
||||
description: 'Analytics, monitoring, and insights features',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -905,7 +934,8 @@ ${contextSection}${existingWorkSection}`;
|
||||
|
||||
/**
|
||||
* Get all guided prompts
|
||||
* NOTE: Keep in sync with apps/ui/src/components/views/ideation-view/data/guided-prompts.ts
|
||||
* This is the single source of truth for guided prompts data.
|
||||
* Frontend fetches this data via /api/ideation/prompts endpoint.
|
||||
*/
|
||||
getAllPrompts(): IdeationPrompt[] {
|
||||
return [
|
||||
@@ -1629,7 +1659,20 @@ Focus on practical, implementable suggestions that would genuinely improve the p
|
||||
return `${summary}. Found ${suggestions.length} improvement opportunities${highPriority > 0 ? ` (${highPriority} high priority)` : ''}.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map idea category to feature category
|
||||
* Used internally for idea-to-feature conversion
|
||||
*/
|
||||
private mapIdeaCategoryToFeatureCategory(category: IdeaCategory): string {
|
||||
return this.mapSuggestionCategoryToFeatureCategory(category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map suggestion/idea category to feature category
|
||||
* This is the single source of truth for category mapping.
|
||||
* Used by both idea-to-feature conversion and suggestion-to-feature conversion.
|
||||
*/
|
||||
mapSuggestionCategoryToFeatureCategory(category: IdeaCategory): string {
|
||||
const mapping: Record<IdeaCategory, string> = {
|
||||
feature: 'ui',
|
||||
'ux-ui': 'enhancement',
|
||||
|
||||
Reference in New Issue
Block a user