Spaces:
Sleeping
Sleeping
Download src/modules/document-generation/document-generation.service.ts from mahrooshishaq/SmartfolioBackend: direct link, hf CLI and curl.
- Browser
- Download file 5.95 kB
-
https://huggingface.co/spaces/mahrooshishaq/SmartfolioBackend/resolve/main/src/modules/document-generation/document-generation.service.ts
- Command line
-
hf download hf://spaces/mahrooshishaq/SmartfolioBackend/src/modules/document-generation/document-generation.service.ts
-
curl -L -o document-generation.service.ts https://huggingface.co/spaces/mahrooshishaq/SmartfolioBackend/resolve/main/src/modules/document-generation/document-generation.service.ts
5.95 kB
| import { Injectable, Logger, BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common'; | |
| import { InjectRepository } from '@nestjs/typeorm'; | |
| import { Repository } from 'typeorm'; | |
| import { GroqService } from '../ai/groq.service'; | |
| import { | |
| GeneratedDocument, | |
| DocumentType, | |
| } from './entities/generated-document.entity'; | |
| import { GenerateDocumentDto } from './dto/generate-document.dto'; | |
| import { UserProfile } from '../users/entities/user-profile.entity'; | |
| import { User } from '../users/user.entity'; | |
| import { | |
| COVER_LETTER_PROMPT, | |
| PROFESSIONAL_EMAIL_PROMPT, | |
| UNIVERSITY_STATEMENT_PROMPT, | |
| } from './prompts/document.prompts'; | |
| () | |
| export class DocumentGenerationService { | |
| private readonly logger = new Logger(DocumentGenerationService.name); | |
| constructor( | |
| (GeneratedDocument) | |
| private readonly docRepo: Repository<GeneratedDocument>, | |
| (UserProfile) | |
| private readonly profileRepo: Repository<UserProfile>, | |
| (User) | |
| private readonly userRepo: Repository<User>, | |
| private readonly groq: GroqService, | |
| ) {} | |
| async generate( | |
| userId: string, | |
| dto: GenerateDocumentDto, | |
| ): Promise<{ documentId: string; content: string; title: string }> { | |
| this.validateFormData(dto.documentType, dto.formData); | |
| const [user, profile] = await Promise.all([ | |
| this.userRepo.findOne({ where: { id: userId } }), | |
| this.profileRepo.findOne({ where: { userId } }), | |
| ]); | |
| const profileBlock = this.buildProfileBlock(user, profile); | |
| const systemPrompt = this.getSystemPrompt(dto.documentType); | |
| const userMessage = this.buildUserMessage(dto, profileBlock); | |
| const content = await this.groq.analyzeWithSystemPrompt( | |
| systemPrompt, | |
| userMessage, | |
| { temperature: 0.7, maxTokens: 1500 }, | |
| ); | |
| const cleaned = content.trim(); | |
| const title = this.buildTitle(dto.documentType, dto.formData); | |
| const saved = await this.docRepo.save( | |
| this.docRepo.create({ | |
| userId, | |
| documentType: dto.documentType, | |
| formData: dto.formData, | |
| content: cleaned, | |
| title, | |
| }), | |
| ); | |
| return { documentId: saved.id, content: cleaned, title }; | |
| } | |
| async getHistory(userId: string) { | |
| const docs = await this.docRepo.find({ | |
| where: { userId }, | |
| order: { createdAt: 'DESC' }, | |
| }); | |
| return docs.map((d) => ({ | |
| id: d.id, | |
| documentType: d.documentType, | |
| title: d.title, | |
| preview: d.content.slice(0, 200), | |
| createdAt: d.createdAt, | |
| })); | |
| } | |
| async getDocumentDetail(userId: string, docId: string) { | |
| const doc = await this.docRepo.findOne({ where: { id: docId } }); | |
| if (!doc) throw new NotFoundException('Document not found.'); | |
| if (doc.userId !== userId) throw new ForbiddenException('Access denied.'); | |
| return doc; | |
| } | |
| // βββ helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| private validateFormData(type: DocumentType, data: Record<string, any>) { | |
| const requiredByType: Record<DocumentType, string[]> = { | |
| [DocumentType.COVER_LETTER]: ['companyName', 'position'], | |
| [DocumentType.PROFESSIONAL_EMAIL]: ['recipient', 'purpose'], | |
| [DocumentType.UNIVERSITY_STATEMENT]: ['universityName', 'program', 'motivation'], | |
| }; | |
| const required = requiredByType[type]; | |
| const missing = required.filter((f) => !data[f] || String(data[f]).trim().length === 0); | |
| if (missing.length > 0) { | |
| throw new BadRequestException(`Missing required fields: ${missing.join(', ')}`); | |
| } | |
| } | |
| private getSystemPrompt(type: DocumentType): string { | |
| switch (type) { | |
| case DocumentType.COVER_LETTER: | |
| return COVER_LETTER_PROMPT; | |
| case DocumentType.PROFESSIONAL_EMAIL: | |
| return PROFESSIONAL_EMAIL_PROMPT; | |
| case DocumentType.UNIVERSITY_STATEMENT: | |
| return UNIVERSITY_STATEMENT_PROMPT; | |
| } | |
| } | |
| private buildProfileBlock(user: User | null, profile: UserProfile | null): string { | |
| const lines: string[] = []; | |
| if (user?.name) lines.push(`Name: ${user.name}`); | |
| if (profile) { | |
| if (profile.currentRole) lines.push(`Current Role: ${profile.currentRole}`); | |
| if (profile.targetRole) lines.push(`Target Role: ${profile.targetRole}`); | |
| if (profile.experienceLevel) lines.push(`Experience Level: ${profile.experienceLevel}`); | |
| if (profile.yearsOfExperience != null) lines.push(`Years of Experience: ${profile.yearsOfExperience}`); | |
| if (profile.educationLevel) lines.push(`Education: ${profile.educationLevel}`); | |
| if (profile.skills?.length) lines.push(`Skills: ${profile.skills.join(', ')}`); | |
| if (profile.interests?.length) lines.push(`Interests: ${profile.interests.join(', ')}`); | |
| if (profile.location) lines.push(`Location: ${profile.location}`); | |
| } | |
| return lines.length > 0 | |
| ? `Applicant profile:\n${lines.join('\n')}` | |
| : 'Applicant profile: (not yet completed)'; | |
| } | |
| private buildUserMessage(dto: GenerateDocumentDto, profileBlock: string): string { | |
| const { documentType, formData } = dto; | |
| const formBlock = `Form details:\n${Object.entries(formData) | |
| .filter(([_, v]) => v != null && String(v).trim().length > 0) | |
| .map(([k, v]) => `${k}: ${v}`) | |
| .join('\n')}`; | |
| return `${profileBlock}\n\n${formBlock}\n\nGenerate the ${documentType.replace('_', ' ')} now.`; | |
| } | |
| private buildTitle(type: DocumentType, data: Record<string, any>): string { | |
| switch (type) { | |
| case DocumentType.COVER_LETTER: | |
| return `Cover Letter: ${data.companyName ?? 'Untitled'}`; | |
| case DocumentType.PROFESSIONAL_EMAIL: | |
| return `Email: ${data.subject || data.purpose?.slice(0, 40) || 'Untitled'}`; | |
| case DocumentType.UNIVERSITY_STATEMENT: | |
| return `Personal Statement: ${data.universityName ?? 'Untitled'}`; | |
| } | |
| } | |
| } | |