Skip to main content
Glama

UNO-MCP

analyzer.ts28.3 kB
/** * TextAnalyzer class implements the Analysis Phase of UNO. * It performs contextual assessment and enhancement evaluation on story text. */ export class TextAnalyzer { /** * Analyzes the given text and generates a comprehensive report. * * @param text The story page text to analyze * @returns A formatted Markdown report with analysis results */ async analyzeText(text: string): Promise<string> { const wordCount = this.countWords(text); const charCount = text.length; const contextualAssessment = await this.performContextualAssessment(text); const enhancementEvaluation = await this.performEnhancementEvaluation(text); const repetitionPatterns = await this.identifyRepetitionPatterns(text); // Generate a markdown report return this.generateReport(text, wordCount, charCount, contextualAssessment, enhancementEvaluation, repetitionPatterns); } /** * Counts the number of words in the text. * * @param text The text to count words in * @returns The number of words */ countWords(text: string): number { return text.split(/\s+/).filter(word => word.length > 0).length; } /** * Performs contextual assessment to determine the text's position in narrative arc, * character focus, scene type, mood, etc. * * @param text The text to assess * @returns Object containing contextual assessment results */ async performContextualAssessment(text: string): Promise<any> { // Analyze the text to determine: const narrativePosition = this.determineNarrativePosition(text); const characterFocus = this.identifyCharacterFocus(text); const sceneType = this.determineSceneType(text); const moodAndTone = this.analyzeMoodAndTone(text); return { narrativePosition, characterFocus, sceneType, moodAndTone }; } /** * Performs enhancement evaluation to determine which techniques * would be most beneficial for this text. * * @param text The text to evaluate * @returns Object containing enhancement recommendations */ async performEnhancementEvaluation(text: string): Promise<any> { // Determine if the text would benefit from each enhancement technique const needsGoldenShadow = this.evaluateGoldenShadowNeed(text); const needsEnvironmentalExpansion = this.evaluateEnvironmentalExpansionNeed(text); const needsActionSceneEnhancement = this.evaluateActionSceneNeed(text); const needsProseSmoothening = this.evaluateProseSmoothingNeed(text); const repetitionSeverity = this.evaluateRepetitionSeverity(text); return { needsGoldenShadow, needsEnvironmentalExpansion, needsActionSceneEnhancement, needsProseSmoothening, repetitionSeverity }; } /** * Identifies repetition patterns in the text for the Repetition Elimination phase. * * @param text The text to analyze for repetitions * @returns Object containing identified repetition patterns */ async identifyRepetitionPatterns(text: string): Promise<any> { // Find repeated words, phrases, and sentence structures const repeatedWords = this.findRepeatedWords(text); const repeatedPhrases = this.findRepeatedPhrases(text); const repeatedSentenceStructures = this.findRepeatedSentenceStructures(text); return { repeatedWords, repeatedPhrases, repeatedSentenceStructures }; } /** * Determines the position of the text in the overall narrative arc. * * @param text The text to analyze * @returns Information about the narrative position */ determineNarrativePosition(text: string): any { // This would use natural language analysis to determine if the text appears to be: // - An introduction/beginning // - Rising action // - Climax // - Falling action // - Resolution/ending // For our implementation, we'll use some heuristics: const hasIntroductionMarkers = this.containsPatterns(text, [ "began", "started", "first time", "introduction", "met", "once upon" ]); const hasClimaxMarkers = this.containsPatterns(text, [ "finally", "suddenly", "at last", "climax", "confrontation", "face to face", "showdown" ]); const hasResolutionMarkers = this.containsPatterns(text, [ "ended", "finished", "resolved", "conclusion", "epilogue", "aftermath", "settled" ]); // Calculate rough position let position = "middle/developing"; if (hasIntroductionMarkers) position = "beginning/introduction"; if (hasClimaxMarkers) position = "climax/turning point"; if (hasResolutionMarkers) position = "resolution/ending"; return { position, isIntroduction: hasIntroductionMarkers, isClimax: hasClimaxMarkers, isResolution: hasResolutionMarkers }; } /** * Identifies the primary character focus in the text. * * @param text The text to analyze * @returns Information about character focus */ identifyCharacterFocus(text: string): any { // Find character names and count mentions const potentialNames = this.extractPotentialCharacterNames(text); const pronounCounts = this.countPronouns(text); // Determine POV const firstPersonPOV = pronounCounts.firstPerson > 0; const thirdPersonPOV = pronounCounts.thirdPerson > 0; return { potentialCharacters: potentialNames, pointOfView: firstPersonPOV ? "first-person" : thirdPersonPOV ? "third-person" : "unclear", pronounDistribution: pronounCounts }; } /** * Determines the dominant scene type (action, dialogue, exposition, etc.) * * @param text The text to analyze * @returns Information about the scene type */ determineSceneType(text: string): any { // Count dialogue markers const dialogueLines = (text.match(/["'].*?["']/g) || []).length; const dialogueTags = (text.match(/said|asked|replied|responded|shouted|whispered/g) || []).length; // Count action markers const actionVerbs = (text.match(/ran|jumped|fought|grabbed|threw|dodged|attacked|rushed|lunged|sprinted|dashed/g) || []).length; // Determine if it's an action scene const isActionScene = actionVerbs > 5; // Determine if it's dialogue-heavy const isDialogueHeavy = dialogueLines > 5 || dialogueTags > 5; // Determine if it's exposition-heavy (description, background, etc.) const isExposition = !isActionScene && !isDialogueHeavy; let dominantType = "mixed"; if (isActionScene) dominantType = "action"; if (isDialogueHeavy) dominantType = "dialogue"; if (isExposition) dominantType = "exposition"; return { dominantType, isAction: isActionScene, isDialogue: isDialogueHeavy, isExposition: isExposition, dialogueCount: dialogueLines, actionVerbCount: actionVerbs }; } /** * Analyzes the mood and tone of the text. * * @param text The text to analyze * @returns Information about mood and tone */ analyzeMoodAndTone(text: string): any { // Check for positive emotional words const positiveWords = this.containsPatterns(text, [ "happy", "joy", "love", "peace", "hope", "exciting", "beautiful", "pleased" ]); // Check for negative emotional words const negativeWords = this.containsPatterns(text, [ "sad", "fear", "anger", "hate", "despair", "darkness", "tragic", "grim" ]); // Check for suspense words const suspenseWords = this.containsPatterns(text, [ "mysterious", "suspense", "tension", "anxious", "uncertain", "danger", "threat" ]); let moodCategory = "neutral"; if (positiveWords) moodCategory = "positive"; if (negativeWords) moodCategory = "negative"; if (suspenseWords) moodCategory = "suspenseful"; return { mood: moodCategory, hasPositiveElements: positiveWords, hasNegativeElements: negativeWords, hasSuspenseElements: suspenseWords }; } /** * Determines if the text would benefit from Golden Shadow enhancement. * * @param text The text to evaluate * @returns Assessment of Golden Shadow enhancement need */ evaluateGoldenShadowNeed(text: string): any { // Check for characters mentioned only briefly const characterNames = this.extractPotentialCharacterNames(text); const underdevelopedCharacters = characterNames.filter(name => this.countOccurrences(text, name) <= 2 ); // Check for briefly mentioned plot elements const plotElements = this.extractPotentialPlotElements(text); const underdevelopedPlotElements = plotElements.length > 0; // Overall assessment const needLevel = underdevelopedCharacters.length > 0 || underdevelopedPlotElements ? "high" : "low"; return { needLevel, underdevelopedCharacters, hasUnderdevelopedPlotElements: underdevelopedPlotElements }; } /** * Determines if the text would benefit from Environmental Expansion. * * @param text The text to evaluate * @returns Assessment of Environmental Expansion need */ evaluateEnvironmentalExpansionNeed(text: string): any { // Check for sensory descriptions const visualDescriptions = this.containsPatterns(text, [ "looked", "saw", "appeared", "watched", "color", "bright", "dark", "shape" ]); const auditoryDescriptions = this.containsPatterns(text, [ "heard", "sound", "noise", "listen", "whisper", "quiet", "loud", "silence" ]); const tactileDescriptions = this.containsPatterns(text, [ "felt", "touch", "rough", "smooth", "texture", "warm", "cold", "soft", "hard" ]); const olfactoryDescriptions = this.containsPatterns(text, [ "smell", "scent", "aroma", "odor", "fragrance", "stench" ]); const sensoryRichness = [ visualDescriptions, auditoryDescriptions, tactileDescriptions, olfactoryDescriptions ].filter(Boolean).length; // Setting description check const hasSettingDescription = this.containsPatterns(text, [ "room", "building", "house", "landscape", "forest", "city", "street", "sky", "mountain" ]); // Overall assessment const needLevel = sensoryRichness < 3 || !hasSettingDescription ? "high" : "low"; return { needLevel, sensoryRichness, hasSufficientVisualDescriptions: visualDescriptions, hasSufficientAuditoryDescriptions: auditoryDescriptions, hasSufficientTactileDescriptions: tactileDescriptions, hasSufficientOlfactoryDescriptions: olfactoryDescriptions, hasSettingDescription }; } /** * Determines if the text would benefit from Action Scene Enhancement. * * @param text The text to evaluate * @returns Assessment of Action Scene Enhancement need */ evaluateActionSceneNeed(text: string): any { // First check if it's an action scene at all const sceneType = this.determineSceneType(text); if (!sceneType.isAction) { return { needLevel: "not applicable", isActionScene: false }; } // Check for time manipulation const hasTimeManipulation = this.containsPatterns(text, [ "slow", "quickly", "moment", "instant", "suddenly", "time seemed", "freeze" ]); // Check for sensory details in action const hasSensoryDetails = this.containsPatterns(text, [ "felt", "heard", "saw", "smell", "taste", "pain", "heart pounding" ]); // Check for environmental interaction const hasEnvironmentalInteraction = this.containsPatterns(text, [ "against the", "through the", "over the", "under the", "from the" ]); // Overall assessment const needLevel = (!hasTimeManipulation || !hasSensoryDetails || !hasEnvironmentalInteraction) ? "high" : "medium"; return { needLevel, isActionScene: true, hasTimeManipulation, hasSensoryDetails, hasEnvironmentalInteraction }; } /** * Determines if the text would benefit from Prose Smoothing. * * @param text The text to evaluate * @returns Assessment of Prose Smoothing need */ evaluateProseSmoothingNeed(text: string): any { // Check for sentence length variety const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0); const sentenceLengths = sentences.map(s => s.split(/\s+/).length); // Calculate statistics const averageLength = sentenceLengths.reduce((sum, len) => sum + len, 0) / sentenceLengths.length; const sentenceLengthVariety = new Set(sentenceLengths).size / sentenceLengths.length; // Check for transition words const hasTransitionWords = this.containsPatterns(text, [ "however", "therefore", "furthermore", "additionally", "consequently", "meanwhile" ]); // Check for paragraph structure const paragraphs = text.split(/\n+/).filter(p => p.trim().length > 0); const hasVariedParagraphLength = new Set(paragraphs.map(p => p.length)).size > 1; // Overall assessment const needLevel = (sentenceLengthVariety < 0.3 || !hasTransitionWords || !hasVariedParagraphLength) ? "high" : "low"; return { needLevel, averageSentenceLength: averageLength, sentenceLengthVariety, hasTransitionWords, hasVariedParagraphLength }; } /** * Evaluates the severity of repetition in the text. * * @param text The text to evaluate * @returns Assessment of repetition severity */ evaluateRepetitionSeverity(text: string): any { // Find repeated words const words = text.toLowerCase().match(/\b\w+\b/g) || []; const wordFrequency: {[key: string]: number} = {}; words.forEach((word: string): void => { if (word.length > 3) { // Only consider words longer than 3 letters wordFrequency[word] = (wordFrequency[word] || 0) + 1; } }); // Find words repeated more than 3 times const repeatedWords = Object.entries(wordFrequency) .filter(([_, count]) => count > 3) .map(([word, count]) => ({ word, count })); // Overall assessment const repetitionSeverity = repeatedWords.length > 5 ? "high" : repeatedWords.length > 2 ? "medium" : "low"; return { severity: repetitionSeverity, repeatedWordCount: repeatedWords.length, topRepeatedWords: repeatedWords.sort((a, b) => b.count - a.count).slice(0, 5) }; } /** * Finds repeated words in the text. * * @param text The text to analyze * @returns Array of repeated words with counts */ findRepeatedWords(text: string): Array<{word: string, count: number}> { const words = text.toLowerCase().match(/\b\w+\b/g) || []; const wordFrequency: {[key: string]: number} = {}; words.forEach(word => { if (word.length > 3) { // Only consider words longer than 3 letters wordFrequency[word] = (wordFrequency[word] || 0) + 1; } }); // Find words repeated more than twice return Object.entries(wordFrequency) .filter(([_, count]) => count > 2) .map(([word, count]) => ({ word, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); // Return top 10 repeated words } /** * Finds repeated phrases in the text. * * @param text The text to analyze * @returns Array of repeated phrases with counts */ findRepeatedPhrases(text: string): Array<{phrase: string, count: number}> { // Extract 3-5 word phrases and check for repetition const phrases: {[key: string]: number} = {}; const words = text.split(/\s+/); // Look for 3-word phrases for (let i = 0; i < words.length - 2; i++) { const phrase = words.slice(i, i + 3).join(' ').toLowerCase(); phrases[phrase] = (phrases[phrase] || 0) + 1; } // Find phrases repeated more than once return Object.entries(phrases) .filter(([_, count]) => count > 1) .map(([phrase, count]) => ({ phrase, count })) .sort((a, b) => b.count - a.count) .slice(0, 5); // Return top 5 repeated phrases } /** * Finds repeated sentence structures in the text. * * @param text The text to analyze * @returns Array of repeated structures with counts */ findRepeatedSentenceStructures(text: string): Array<{pattern: string, count: number}> { // This is a simplified approach - in a real implementation, // we would use more sophisticated NLP techniques const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0); const patterns: {[key: string]: number} = {}; // Create a simplified pattern for each sentence sentences.forEach((sentence: string): void => { // Simplified pattern: first word + sentence length category const firstWord = sentence.trim().split(/\s+/)[0].toLowerCase(); const lengthCategory = sentence.split(/\s+/).length < 8 ? "short" : sentence.split(/\s+/).length < 15 ? "medium" : "long"; const pattern = `${firstWord}-${lengthCategory}`; patterns[pattern] = (patterns[pattern] || 0) + 1; }); // Find patterns repeated more than twice return Object.entries(patterns) .filter(([_, count]) => count > 2) .map(([pattern, count]) => ({ pattern, count })) .sort((a, b) => b.count - a.count) .slice(0, 3); // Return top 3 patterns } /** * Extracts potential character names from the text. * * @param text The text to analyze * @returns Array of potential character names */ extractPotentialCharacterNames(text: string): string[] { // This is a simplified approach - in a real implementation, // we would use named entity recognition // Look for capitalized words that aren't at the start of sentences const potentialNames = new Set<string>(); const words = text.split(/\s+/); for (let i = 1; i < words.length; i++) { const word = words[i].replace(/[^\w]/, ''); if (word.length > 0 && word[0] === word[0].toUpperCase() && word[0].match(/[A-Z]/)) { potentialNames.add(word); } } return Array.from(potentialNames); } /** * Extracts potential plot elements from the text. * * @param text The text to analyze * @returns Array of potential plot elements */ extractPotentialPlotElements(text: string): string[] { // This is a simplified approach - in a real implementation, // we would use more sophisticated NLP techniques // Look for phrases that might indicate plot elements const plotIndicators = [ "remembered", "realized", "discovered", "decided", "planned", "secret", "mission", "quest", "journey", "task", "mystery" ]; const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0); const potentialElements = sentences.filter(sentence => plotIndicators.some(indicator => sentence.toLowerCase().includes(indicator) ) ); return potentialElements.map(s => s.trim()); } /** * Counts pronouns in the text to help determine POV. * * @param text The text to analyze * @returns Object with pronoun counts */ countPronouns(text: string): {firstPerson: number, secondPerson: number, thirdPerson: number} { const firstPersonPronouns = ["I", "me", "my", "mine", "we", "us", "our", "ours"]; const secondPersonPronouns = ["you", "your", "yours"]; const thirdPersonPronouns = ["he", "him", "his", "she", "her", "hers", "they", "them", "their", "theirs"]; const words = text.toLowerCase().split(/\s+/); const firstPerson = words.filter(word => firstPersonPronouns.includes(word.replace(/[^\w]/g, '')) ).length; const secondPerson = words.filter(word => secondPersonPronouns.includes(word.replace(/[^\w]/g, '')) ).length; const thirdPerson = words.filter(word => thirdPersonPronouns.includes(word.replace(/[^\w]/g, '')) ).length; return { firstPerson, secondPerson, thirdPerson }; } /** * Counts occurrences of a word or phrase in text. * * @param text The text to search in * @param word The word or phrase to count * @returns Number of occurrences */ countOccurrences(text: string, word: string): number { const regex = new RegExp(`\\b${word}\\b`, 'gi'); return (text.match(regex) || []).length; } /** * Checks if the text contains any of the patterns in the array. * * @param text The text to check * @param patterns Array of patterns to look for * @returns True if any pattern is found */ containsPatterns(text: string, patterns: string[]): boolean { const lowerText = text.toLowerCase(); return patterns.some(pattern => lowerText.includes(pattern)); } /** * Generates a formatted markdown report with the analysis results. * * @param text Original text * @param wordCount Word count * @param charCount Character count * @param contextualAssessment Contextual assessment results * @param enhancementEvaluation Enhancement evaluation results * @param repetitionPatterns Repetition patterns identified * @returns Formatted markdown report */ generateReport( text: string, wordCount: number, charCount: number, contextualAssessment: any, enhancementEvaluation: any, repetitionPatterns: any ): string { // Calculate target expansion counts const targetWordCount = Math.round(wordCount * 2); const targetCharCount = Math.round(charCount * 2); // Generate the report in markdown format return `# UNO Analysis Report ## Text Statistics - **Original Word Count**: ${wordCount} - **Original Character Count**: ${charCount} - **Target Word Count (200%)**: ${targetWordCount} - **Target Character Count (200%)**: ${targetCharCount} ## Contextual Assessment ### Narrative Position - **Position**: ${contextualAssessment.narrativePosition.position} - **Introduction Markers**: ${contextualAssessment.narrativePosition.isIntroduction ? "Present" : "Absent"} - **Climax Markers**: ${contextualAssessment.narrativePosition.isClimax ? "Present" : "Absent"} - **Resolution Markers**: ${contextualAssessment.narrativePosition.isResolution ? "Present" : "Absent"} ### Character Focus - **Point of View**: ${contextualAssessment.characterFocus.pointOfView} - **Potential Characters**: ${contextualAssessment.characterFocus.potentialCharacters.join(", ") || "None identified"} - **Pronoun Distribution**: - First Person: ${contextualAssessment.characterFocus.pronounDistribution.firstPerson} - Second Person: ${contextualAssessment.characterFocus.pronounDistribution.secondPerson} - Third Person: ${contextualAssessment.characterFocus.pronounDistribution.thirdPerson} ### Scene Type - **Dominant Type**: ${contextualAssessment.sceneType.dominantType} - **Action Elements**: ${contextualAssessment.sceneType.isAction ? "Strong" : "Weak"} - **Dialogue Elements**: ${contextualAssessment.sceneType.isDialogue ? "Strong" : "Weak"} - **Exposition Elements**: ${contextualAssessment.sceneType.isExposition ? "Strong" : "Weak"} ### Mood and Tone - **Dominant Mood**: ${contextualAssessment.moodAndTone.mood} - **Positive Elements**: ${contextualAssessment.moodAndTone.hasPositiveElements ? "Present" : "Absent"} - **Negative Elements**: ${contextualAssessment.moodAndTone.hasNegativeElements ? "Present" : "Absent"} - **Suspense Elements**: ${contextualAssessment.moodAndTone.hasSuspenseElements ? "Present" : "Absent"} ## Enhancement Recommendations ### Golden Shadow Enhancement - **Need Level**: ${enhancementEvaluation.needsGoldenShadow.needLevel} - **Underdeveloped Characters**: ${enhancementEvaluation.needsGoldenShadow.underdevelopedCharacters.join(", ") || "None identified"} - **Underdeveloped Plot Elements**: ${enhancementEvaluation.needsGoldenShadow.hasUnderdevelopedPlotElements ? "Present" : "Absent"} ### Environmental Expansion - **Need Level**: ${enhancementEvaluation.needsEnvironmentalExpansion.needLevel} - **Sensory Richness (0-4)**: ${enhancementEvaluation.needsEnvironmentalExpansion.sensoryRichness} - **Setting Description**: ${enhancementEvaluation.needsEnvironmentalExpansion.hasSettingDescription ? "Present" : "Absent"} - **Areas to Enhance**: - Visual: ${!enhancementEvaluation.needsEnvironmentalExpansion.hasSufficientVisualDescriptions ? "Needs improvement" : "Sufficient"} - Auditory: ${!enhancementEvaluation.needsEnvironmentalExpansion.hasSufficientAuditoryDescriptions ? "Needs improvement" : "Sufficient"} - Tactile: ${!enhancementEvaluation.needsEnvironmentalExpansion.hasSufficientTactileDescriptions ? "Needs improvement" : "Sufficient"} - Olfactory: ${!enhancementEvaluation.needsEnvironmentalExpansion.hasSufficientOlfactoryDescriptions ? "Needs improvement" : "Sufficient"} ### Action Scene Enhancement - **Applicability**: ${enhancementEvaluation.needsActionSceneEnhancement.isActionScene ? "Applicable" : "Not applicable"} ${enhancementEvaluation.needsActionSceneEnhancement.isActionScene ? `- **Need Level**: ${enhancementEvaluation.needsActionSceneEnhancement.needLevel} - **Time Manipulation**: ${enhancementEvaluation.needsActionSceneEnhancement.hasTimeManipulation ? "Present" : "Absent"} - **Sensory Details**: ${enhancementEvaluation.needsActionSceneEnhancement.hasSensoryDetails ? "Present" : "Absent"} - **Environmental Interaction**: ${enhancementEvaluation.needsActionSceneEnhancement.hasEnvironmentalInteraction ? "Present" : "Absent"}` : ""} ### Prose Smoothing - **Need Level**: ${enhancementEvaluation.needsProseSmoothening.needLevel} - **Average Sentence Length**: ${enhancementEvaluation.needsProseSmoothening.averageSentenceLength.toFixed(1)} words - **Sentence Length Variety**: ${(enhancementEvaluation.needsProseSmoothening.sentenceLengthVariety * 100).toFixed(1)}% - **Transition Words**: ${enhancementEvaluation.needsProseSmoothening.hasTransitionWords ? "Present" : "Absent"} - **Paragraph Length Variety**: ${enhancementEvaluation.needsProseSmoothening.hasVariedParagraphLength ? "Present" : "Absent"} ### Repetition Elimination - **Severity**: ${enhancementEvaluation.repetitionSeverity.severity} - **Repeated Word Count**: ${enhancementEvaluation.repetitionSeverity.repeatedWordCount} - **Top Repeated Words**: ${enhancementEvaluation.repetitionSeverity.topRepeatedWords.map((item: {word: string, count: number}) => ` - "${item.word}" (${item.count} times)`).join('\n')} ## Repetition Patterns ### Repeated Words ${repetitionPatterns.repeatedWords.length > 0 ? repetitionPatterns.repeatedWords.map((item: {word: string, count: number}) => `- "${item.word}" (${item.count} times)`).join('\n') : "- No significant word repetition detected"} ### Repeated Phrases ${repetitionPatterns.repeatedPhrases.length > 0 ? repetitionPatterns.repeatedPhrases.map((item: {phrase: string, count: number}) => `- "${item.phrase}" (${item.count} times)`).join('\n') : "- No significant phrase repetition detected"} ### Repeated Sentence Structures ${repetitionPatterns.repeatedSentenceStructures.length > 0 ? repetitionPatterns.repeatedSentenceStructures.map((item: {pattern: string, count: number}) => `- ${item.pattern} pattern (${item.count} times)`).join('\n') : "- No significant sentence structure repetition detected"} `; } }

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/MushroomFleet/UNO-MCP'

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