Skip to main content
Glama
emotion.ts5.97 kB
/** * Emotion Routes * * REST API endpoints for emotion detection operations. * Requirements: 8.1, 8.2, 8.3 */ import { Router, type Request, type Response } from "express"; import { z } from "zod"; import { DiscreteEmotionClassifier } from "../../emotion/discrete-emotion-classifier.js"; import type { EmotionClassification, EmotionType } from "../../emotion/types.js"; import type { CognitiveCore } from "../cognitive-core.js"; import { asyncHandler, ValidationApiError } from "../middleware/error-handler.js"; import { buildSuccessResponse } from "../types/api-response.js"; /** * Helper to extract request ID from request */ function getRequestId(req: Request): string | undefined { return (req as Request & { requestId?: string }).requestId; } /** * Helper to parse Zod validation errors into field errors */ function parseZodErrors(error: z.ZodError): Record<string, string> { const fieldErrors: Record<string, string> = {}; for (const issue of error.issues) { const path = issue.path.join(".") || "request"; fieldErrors[path] = issue.message; } return fieldErrors; } /** * Zod schema for emotion detect request validation * Requirements: 8.1, 8.3 */ const emotionDetectRequestSchema = z.object({ text: z .string() .min(1, "text is required") .max(100000, "text must be at most 100,000 characters"), context: z.string().max(5000, "context must be at most 5,000 characters").optional(), includeDiscrete: z.boolean().optional().default(true), }); /** * Circumplex values in response * Requirements: 8.1 */ interface CircumplexResponse { /** Emotional positivity/negativity (-1 to +1) */ valence: number; /** Emotional intensity/activation (-1 to +1) */ arousal: number; /** Sense of control/power (-1 to +1) */ dominance: number; } /** * Discrete emotion in response * Requirements: 8.2 */ interface DiscreteEmotionResponse { /** Emotion type */ emotion: EmotionType; /** Confidence score (0 to 1) */ confidence: number; /** Intensity rating (0 to 1) */ intensity: number; } /** * Response type for emotion detect endpoint * Requirements: 8.1, 8.2 */ interface EmotionDetectResponse { /** Circumplex model values */ circumplex: CircumplexResponse; /** Discrete emotion classifications */ discrete: DiscreteEmotionResponse[]; } /** * All 11 supported emotion types * Requirements: 8.2 */ const ALL_EMOTION_TYPES: EmotionType[] = [ "joy", "sadness", "anger", "fear", "disgust", "surprise", "pride", "shame", "guilt", "gratitude", "awe", ]; /** * Convert emotion classifications to response format with all 11 emotions * Requirements: 8.2 */ function convertToDiscreteResponse( classifications: EmotionClassification[] ): DiscreteEmotionResponse[] { // Create a map of detected emotions const detectedMap = new Map<EmotionType, EmotionClassification>(); for (const classification of classifications) { detectedMap.set(classification.emotion, classification); } // Return all 11 emotions with their scores (0 if not detected) return ALL_EMOTION_TYPES.map((emotion) => { const detected = detectedMap.get(emotion); return { emotion, confidence: detected?.confidence ?? 0, intensity: detected?.intensity ?? 0, }; }); } /** * Handler for POST /api/v1/emotion/detect * Requirements: 8.1, 8.2, 8.3 * * Detects emotions in text using Circumplex model and discrete emotion classification. * Returns valence, arousal, dominance values and discrete emotion classifications. */ function createEmotionDetectHandler( cognitiveCore: CognitiveCore ): (req: Request, res: Response, next: import("express").NextFunction) => void { // Create discrete classifier instance (reused across requests) const discreteClassifier = new DiscreteEmotionClassifier({ name: "lexicon-based", version: "1.0.0", }); return asyncHandler(async (req: Request, res: Response): Promise<void> => { const requestId = getRequestId(req); const startTime = Date.now(); // Validate request body const parseResult = emotionDetectRequestSchema.safeParse(req.body); if (!parseResult.success) { throw new ValidationApiError(parseZodErrors(parseResult.error)); } const { text, context, includeDiscrete } = parseResult.data; // Analyze text with Circumplex model // Context parameter is used to improve accuracy by providing additional context // The CircumplexEmotionAnalyzer uses the text directly; context can be prepended // for improved accuracy when provided const textToAnalyze = context ? `${context}: ${text}` : text; const circumplexState = cognitiveCore.emotionAnalyzer.analyzeCircumplex(textToAnalyze); // Build circumplex response const circumplex: CircumplexResponse = { valence: circumplexState.valence, arousal: circumplexState.arousal, dominance: circumplexState.dominance, }; // Get discrete emotion classifications if requested let discrete: DiscreteEmotionResponse[] = []; if (includeDiscrete) { const classifications = discreteClassifier.classify(textToAnalyze); discrete = convertToDiscreteResponse(classifications); } // Build response const responseData: EmotionDetectResponse = { circumplex, discrete, }; res.status(200).json(buildSuccessResponse(responseData, { requestId, startTime })); }); } /** * Create emotion routes * * @param cognitiveCore - Shared cognitive core instance * @returns Express router with emotion endpoints */ export function createEmotionRoutes(cognitiveCore: CognitiveCore): Router { const router = Router(); // POST /api/v1/emotion/detect - Detect emotions in text // Requirements: 8.1, 8.2, 8.3 router.post("/detect", createEmotionDetectHandler(cognitiveCore)); return router; } // Export types for testing export type { CircumplexResponse, DiscreteEmotionResponse, EmotionDetectResponse };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server