import { Inject, Injectable, forwardRef } from '@nestjs/common' import { Logger } from '@nestjs/common' import Delta, { Op } from 'quill-delta' import { OpenaiService, PromptType } from 'src/OpenAI/openai.service' import { PrismaService } from 'src/Prisma/prisma.service' import { PubSubService } from 'src/PubSub/pubsub.service' import { RedisService } from 'src/Redis/redis.service' import { MinioService } from '../Minio/minio.service' import { DocumentEvent } from './document.event' import { DocumentDelta } from './document.type' @Injectable() export class DocumentService { constructor( private readonly prisma: PrismaService, private readonly minio: MinioService, private readonly openai: OpenaiService, private readonly redis: RedisService, private readonly pubSub: PubSubService, ) {} // page to list paragraph as delta async pageToParagraph(content: Delta): Promise { // Create a new Delta to store paragraphs const paragraphDelta = new Delta() // Iterate through the operations in the content Delta for (const op of content.ops) { // If the operation is a string insert if (typeof op.insert === 'string') { // Split the text by newline characters const paragraphs = op.insert.split('\n').filter((p) => p.trim() !== '') // Add each non-empty paragraph as a separate insert with a newline paragraphs.forEach((paragraph, index) => { paragraphDelta.insert(paragraph) // Add a newline after each paragraph except the last one if (index < paragraphs.length - 1) { paragraphDelta.insert('\n') } }) } else { // If the operation is not a string (e.g., an embedded object), // add it to the paragraphDelta as-is paragraphDelta.push(op) } } return paragraphDelta } async paragraphToSentence(content: Delta): Promise { // Create a new Delta to store sentences const sentenceDelta = new Delta() // Iterate through the operations in the content Delta for (const op of content.ops) { // If the operation is a string insert if (typeof op.insert === 'string') { // Split the text into paragraphs first const paragraphs = op.insert.split('\n').filter((p) => p.trim() !== '') paragraphs.forEach((paragraph, paragraphIndex) => { // Split paragraph into sentences using regex // This handles common sentence-ending punctuation: . ! ? const sentences = paragraph.split(/(?<=[.!?])\s+/).filter((s) => s.trim() !== '') sentences.forEach((sentence, sentenceIndex) => { sentenceDelta.insert(sentence.trim()) // Add a newline after each sentence except the last one in the paragraph if (sentenceIndex < sentences.length - 1) { sentenceDelta.insert('\n') } }) // Add a newline between paragraphs, except after the last paragraph if (paragraphIndex < paragraphs.length - 1) { sentenceDelta.insert('\n') } }) } else { // If the operation is not a string (e.g., an embedded object), // add it to the sentenceDelta as-is sentenceDelta.push(op) } } return sentenceDelta } async pageToSentence(documentId: string, pageId: number): Promise { const content = await this.minio.getDocumentContent(documentId, pageId) const paragraphDelta = await this.pageToParagraph(content) const sentenceDelta = await this.paragraphToSentence(paragraphDelta) return sentenceDelta } // check grammar for a page by parallely send each sentence to OpenAI service as text and get the result, after that return the result as delta and publish the result to the document // async checkGrammarForPage(documentId: string, pageId: number): Promise { // const sentenceDelta = await this.pageToSentence(documentId, pageId) // // Extract the entire page content as a single text // const pageText = sentenceDelta.ops // .filter((op) => typeof op.insert === 'string') // .map((op) => op.insert as string) // .join(' ') // // Create a unique cache key for the entire page // const cacheKey = `grammar_check:${documentId}:${pageId}` // // Try to get cached result first // const cachedResult = await this.redis.get(cacheKey) // if (cachedResult) { // Logger.log('Cached grammar check result exists', 'Grammar Check') // return // } // // Process the entire page text // const grammarCheckResult = await this.openai.processText(pageText, 0, PromptType.CHECK_GRAMMAR) // if (!grammarCheckResult.result) { // return // } // // Cache the result // await this.redis.setPermanent(cacheKey, JSON.stringify(grammarCheckResult)) // // Calculate diff between original page and corrected page // const diff = await this.diff(pageText, grammarCheckResult.result) // // Build payload // // Publish the result to the subscriber // this.pubSub.publish(`${DocumentEvent.AI_SUGGESTION}.${documentId}.${pageId}`, payload) // } // async diff(original: string, corrected: string): Promise { // const originalDelta = new Delta().insert(original) // const correctedDelta = new Delta().insert(corrected) // const diff = originalDelta.diff(correctedDelta) // Logger.log(diff, 'diff') // return diff // } }