Skip to main content
Glama
nlp-service.ts7.27 kB
import { randomUUID } from 'crypto'; import { FormConfig, QuestionConfig, SubmissionBehavior, FormTheme, QuestionType, ValidationRules, ConditionalLogic, LogicOperator, LogicCombinator, ConditionalAction } from '../models'; export interface NlpServiceConfig { // Configuration for the NLP service will be defined in future tasks. } export interface ParsedForm { title?: string | undefined; description?: string | undefined; questions: ExtractedQuestion[]; } export interface ExtractedQuestion { rawText: string; type?: QuestionType; title?: string; description?: string; options?: string[]; validation?: ValidationRules; logic?: ConditionalLogic; } export interface ModificationCommand { action: 'add' | 'remove' | 'update'; entity: 'question' | 'title' | 'description'; payload: any; } export class NlpService { private questionTypeMap: Map<string, QuestionType>; constructor(_config: NlpServiceConfig = {}) { this.questionTypeMap = new Map([ ['text', QuestionType.TEXT], ['long text', QuestionType.TEXTAREA], ['email', QuestionType.EMAIL], ['number', QuestionType.NUMBER], ['phone', QuestionType.PHONE], ['url', QuestionType.URL], ['date', QuestionType.DATE], ['time', QuestionType.TIME], ['rating', QuestionType.RATING], ['file', QuestionType.FILE], ['signature', QuestionType.SIGNATURE], ['payment', QuestionType.PAYMENT], ['choice', QuestionType.MULTIPLE_CHOICE], ['select', QuestionType.DROPDOWN], ['checkboxes', QuestionType.CHECKBOXES], ['scale', QuestionType.LINEAR_SCALE], ]); } public parse(prompt: string): ParsedForm { const lines = prompt.trim().split('\n').filter(line => line.trim() !== ''); if (lines.length === 0) { return { questions: [] }; } let title: string | undefined = lines.shift(); let description: string | undefined; if (lines.length > 0) { const nextLine = lines[0]; if (nextLine && !this.isQuestion(nextLine)) { description = lines.shift(); } } const questions = lines.map(line => this.extractQuestionDetails(line)); return { title, description, questions }; } private isQuestion(line: string): boolean { const trimmed = line.trim(); return trimmed.includes('?') || /^\d+[:.]/.test(trimmed) || this.questionTypeMap.has(trimmed.split(' ')[0]?.toLowerCase() || ''); } private extractQuestionDetails(line: string): ExtractedQuestion { const title = line.replace(/\s*\(([^)]+)\)\s*$/, '').trim(); const typeMatch = line.match(/\(([^)]+)\)/); const type = (typeMatch && typeMatch[1] && this.questionTypeMap.get(typeMatch[1].toLowerCase())) || QuestionType.TEXT; const extractedQuestion: ExtractedQuestion = { rawText: line, title, type, validation: this.extractValidationRules(line), }; const logic = this.extractConditionalLogic(line); if (logic) { extractedQuestion.logic = logic; } return extractedQuestion; } private extractValidationRules(line: string): ValidationRules { const rules: ValidationRules = {}; if (line.toLowerCase().includes('required')) { rules.required = true; } return rules; } private extractConditionalLogic(line: string): ConditionalLogic | undefined { const logicMatch = line.match(/if question (\d+) is "([^"]+)" then (show|hide) this question/i); if (logicMatch && logicMatch[1] && logicMatch[2] && logicMatch[3]) { const [, triggerQuestionId, triggerValue, action] = logicMatch; return { conditionGroup: { combinator: LogicCombinator.AND, conditions: [{ questionId: triggerQuestionId, operator: LogicOperator.EQUALS, value: triggerValue }], }, actions: [{ action: action.toLowerCase() === 'show' ? ConditionalAction.SHOW : ConditionalAction.HIDE }], }; } return undefined; } public generateFormConfig(prompt: string): FormConfig { const parsed = this.parse(prompt); const questions: QuestionConfig[] = parsed.questions.map((q) => ({ id: randomUUID(), type: q.type || QuestionType.TEXT, label: q.title || 'Untitled Question', required: q.validation?.required || false, validation: q.validation, logic: q.logic, } as QuestionConfig)); return { title: parsed.title ?? 'Untitled Form', questions, settings: { submissionBehavior: SubmissionBehavior.MESSAGE, submissionMessage: 'Thanks!', }, branding: { theme: FormTheme.DEFAULT, }, }; } public customizeFormConfig(prompt: string, baseConfig: FormConfig): FormConfig { const commands = this.parseModificationCommands(prompt); let modifiedConfig = JSON.parse(JSON.stringify(baseConfig)); for (const command of commands) { modifiedConfig = this.applyCommand(command, modifiedConfig); } return modifiedConfig; } private parseModificationCommands(prompt: string): ModificationCommand[] { // Basic parsing logic. This would be replaced with a more sophisticated NLP model in a real scenario. const commands: ModificationCommand[] = []; const lines = prompt.trim().split('\n').map(line => line.trim()).filter(Boolean); for (const line of lines) { if (line.toLowerCase().startsWith('add question:')) { const questionText = line.substring('add question:'.length).trim(); const extractedQuestion = this.extractQuestionDetails(questionText); commands.push({ action: 'add', entity: 'question', payload: { id: randomUUID(), type: extractedQuestion.type || QuestionType.TEXT, label: extractedQuestion.title || 'Untitled Question', required: extractedQuestion.validation?.required || false, validation: extractedQuestion.validation, logic: extractedQuestion.logic, }, }); } else if (line.toLowerCase().startsWith('remove question:')) { const questionLabel = line.substring('remove question:'.length).trim(); commands.push({ action: 'remove', entity: 'question', payload: { label: questionLabel }, }); } else if (line.toLowerCase().startsWith('update title:')) { const newTitle = line.substring('update title:'.length).trim(); commands.push({ action: 'update', entity: 'title', payload: { title: newTitle }, }); } } return commands; } private applyCommand(command: ModificationCommand, config: FormConfig): FormConfig { switch (command.action) { case 'add': if (command.entity === 'question') { config.questions.push(command.payload as QuestionConfig); } break; case 'remove': if (command.entity === 'question') { config.questions = config.questions.filter(q => q.label !== command.payload.label); } break; case 'update': if (command.entity === 'title') { config.title = command.payload.title; } break; } return config; } }

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/learnwithcc/tally-mcp'

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