Update dependencies in package.json and package-lock.json, refactor CronService to include notification handling for schedule expirations, and enhance DocumentSchema with OpenAI integration for document editing suggestions. Additionally, modify GraphqlModule to include PubSubModule for real-time notifications and improve datetime utility functions for better date formatting. Update epess-database subproject reference to indicate a dirty state.
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ScheduleModule } from '@nestjs/schedule'
|
||||
import { CronService } from './cron.service'
|
||||
import { PubSubModule } from 'src/PubSub/pubsub.module'
|
||||
import { NotificationModule } from 'src/Notification/notification.module'
|
||||
@Module({
|
||||
imports: [ScheduleModule.forRoot()],
|
||||
imports: [ScheduleModule.forRoot(), NotificationModule, PubSubModule],
|
||||
providers: [CronService],
|
||||
})
|
||||
export class CronModule {}
|
||||
|
||||
@@ -3,11 +3,15 @@ import { Cron } from '@nestjs/schedule'
|
||||
import { CronExpression } from '@nestjs/schedule'
|
||||
import { OrderStatus, PaymentStatus, ScheduleDateStatus, ScheduleStatus } from '@prisma/client'
|
||||
import { DateTimeUtils } from 'src/common/utils/datetime.utils'
|
||||
import { NotificationService } from 'src/Notification/notification.service'
|
||||
import { PrismaService } from 'src/Prisma/prisma.service'
|
||||
|
||||
@Injectable()
|
||||
export class CronService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly notificationService: NotificationService,
|
||||
) {}
|
||||
|
||||
// cron every 1 minute to check schedule date status
|
||||
@Cron(CronExpression.EVERY_MINUTE)
|
||||
@@ -97,7 +101,7 @@ export class CronService {
|
||||
|
||||
// handle refund ticket by order, if order status is refunded, disable schedule and remove schedule date in future
|
||||
@Cron(CronExpression.EVERY_MINUTE)
|
||||
async handleRefundTicket() {
|
||||
async taskRefundTicket() {
|
||||
Logger.log('Handling refund ticket', 'handleRefundTicket')
|
||||
const now = DateTimeUtils.now().toJSDate()
|
||||
// get all orders where status is REFUNDED and has schedule.dates in future
|
||||
@@ -139,4 +143,50 @@ export class CronService {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// cron every 1 minute to check if there is any schedule date start in less than 30 minutes
|
||||
@Cron(CronExpression.EVERY_MINUTE)
|
||||
async taskCheckScheduleDateStart() {
|
||||
Logger.log('Checking schedule date start', 'taskCheckScheduleDateStart')
|
||||
const schedules = await this.prisma.schedule.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
scheduleStart: {
|
||||
lt: DateTimeUtils.now().plus({ minutes: 30 }).toJSDate(),
|
||||
},
|
||||
},
|
||||
{
|
||||
status: ScheduleStatus.PUBLISHED,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
Logger.log(`Found ${schedules.length} schedules to check`, 'taskCheckScheduleDateStart')
|
||||
for (const schedule of schedules) {
|
||||
await this.prisma.scheduleDate.updateMany({
|
||||
where: { scheduleId: schedule.id, start: { lt: DateTimeUtils.now().plus({ minutes: 30 }).toJSDate() } },
|
||||
data: { status: ScheduleDateStatus.EXPIRED },
|
||||
})
|
||||
// update schedule status to expired
|
||||
await this.prisma.schedule.update({
|
||||
where: { id: schedule.id },
|
||||
data: { status: ScheduleStatus.EXPIRED },
|
||||
})
|
||||
// send notification to mentor
|
||||
const managedService = await this.prisma.managedService.findUnique({
|
||||
where: { id: schedule.managedServiceId },
|
||||
})
|
||||
if (managedService) {
|
||||
await this.notificationService.sendNotification(
|
||||
managedService.mentorId,
|
||||
'Lịch hướng dẫn của bạn đã hết hạn',
|
||||
`Lịch hướng dẫn với ngày bắt đầu: ${DateTimeUtils.format(
|
||||
DateTimeUtils.fromDate(schedule.scheduleStart),
|
||||
'DD/MM/YYYY',
|
||||
)}, slot: ${schedule.slots.map((s) => s).join(', ')} của bạn đã hết hạn do không có học viên đăng ký, vui lòng tạo lịch hướng dẫn mới`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { DocumentService } from './document.service'
|
||||
import { DocumentSchema } from './document.schema'
|
||||
import { OpenaiModule } from 'src/OpenAI/openai.module'
|
||||
import { JSDOM } from 'jsdom'
|
||||
@Module({
|
||||
imports: [OpenaiModule],
|
||||
providers: [JSDOM, DocumentService, DocumentSchema],
|
||||
exports: [DocumentService, DocumentSchema],
|
||||
})
|
||||
|
||||
@@ -7,12 +7,14 @@ import { Document } from '@prisma/client'
|
||||
import { DocumentDelta } from './document.type'
|
||||
import Delta from 'quill-delta'
|
||||
import { MinioService } from 'src/Minio/minio.service'
|
||||
import { OpenaiService } from 'src/OpenAI/openai.service'
|
||||
@Injectable()
|
||||
export class DocumentSchema extends PothosSchema {
|
||||
constructor(
|
||||
@Inject(SchemaBuilderToken) private readonly builder: Builder,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly minio: MinioService,
|
||||
private readonly openaiService: OpenaiService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -237,6 +239,18 @@ export class DocumentSchema extends PothosSchema {
|
||||
},
|
||||
}),
|
||||
|
||||
testSuggestEditDocument: t.field({
|
||||
type: 'Delta',
|
||||
args: {
|
||||
documentDelta: t.arg({ type: this.documentDeltaInput(), required: true }),
|
||||
},
|
||||
resolve: async (_root, args, ctx: SchemaContext) => {
|
||||
if (ctx.isSubscription) throw new Error('Not allowed')
|
||||
if (!args.documentDelta.delta) throw new Error('Delta not found')
|
||||
return await this.openaiService.documentSuggestEditDelta(args.documentDelta.delta)
|
||||
},
|
||||
}),
|
||||
|
||||
eventDocumentChanged: t.field({
|
||||
type: this.documentDelta(),
|
||||
args: {
|
||||
|
||||
@@ -44,6 +44,8 @@ import { DocumentModule } from 'src/Document/document.module'
|
||||
import { Context } from 'graphql-ws'
|
||||
import { AnalyticModule } from 'src/Analytic/analytic.module'
|
||||
import { MeetingRoomModule } from 'src/MeetingRoom/meetingroom.module'
|
||||
import { PubSubModule } from 'src/PubSub/pubsub.module'
|
||||
import { PubSubService } from 'src/PubSub/pubsub.service'
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
@@ -81,6 +83,7 @@ import { MeetingRoomModule } from 'src/MeetingRoom/meetingroom.module'
|
||||
DocumentModule,
|
||||
AnalyticModule,
|
||||
MeetingRoomModule,
|
||||
PubSubModule,
|
||||
PothosModule.forRoot({
|
||||
builder: {
|
||||
inject: [PrismaService],
|
||||
@@ -89,11 +92,8 @@ import { MeetingRoomModule } from 'src/MeetingRoom/meetingroom.module'
|
||||
}),
|
||||
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
||||
driver: PothosApolloDriver,
|
||||
inject: [GraphqlService, 'PUB_SUB'],
|
||||
useFactory: async (
|
||||
graphqlService: GraphqlService,
|
||||
pubsub: RedisPubSub,
|
||||
) => ({
|
||||
inject: [GraphqlService, 'PUB_SUB_REDIS'],
|
||||
useFactory: async (graphqlService: GraphqlService, pubsub: RedisPubSub) => ({
|
||||
path: process.env.API_PATH + '/graphql',
|
||||
debug: process.env.NODE_ENV === 'development' || false,
|
||||
playground: process.env.NODE_ENV === 'development' || false,
|
||||
@@ -115,8 +115,7 @@ import { MeetingRoomModule } from 'src/MeetingRoom/meetingroom.module'
|
||||
throw new Error('No extra provided')
|
||||
}
|
||||
// @ts-expect-error: TODO
|
||||
ctx.extra.request.headers['x-session-id'] =
|
||||
ctx.connectionParams['x-session-id']
|
||||
ctx.extra.request.headers['x-session-id'] = ctx.connectionParams['x-session-id']
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -153,10 +152,7 @@ import { MeetingRoomModule } from 'src/MeetingRoom/meetingroom.module'
|
||||
req,
|
||||
me: req ? await graphqlService.acquireContext(req) : null,
|
||||
pubSub: pubsub,
|
||||
invalidateCache: () =>
|
||||
graphqlService.invalidateCache(
|
||||
req?.headers['x-session-id'] as string,
|
||||
),
|
||||
invalidateCache: () => graphqlService.invalidateCache(req?.headers['x-session-id'] as string),
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -167,8 +163,7 @@ import { MeetingRoomModule } from 'src/MeetingRoom/meetingroom.module'
|
||||
RedisService,
|
||||
{
|
||||
provide: GraphqlService,
|
||||
useFactory: (prisma: PrismaService, redis: RedisService) =>
|
||||
new GraphqlService(prisma, redis),
|
||||
useFactory: (prisma: PrismaService, redis: RedisService) => new GraphqlService(prisma, redis),
|
||||
inject: [PrismaService, 'REDIS_CLIENT'],
|
||||
},
|
||||
{
|
||||
@@ -181,21 +176,8 @@ import { MeetingRoomModule } from 'src/MeetingRoom/meetingroom.module'
|
||||
useFactory: (builder: Builder) => new PrismaCrudGenerator(builder),
|
||||
inject: [Builder],
|
||||
},
|
||||
{
|
||||
provide: 'PUB_SUB',
|
||||
useFactory: () =>
|
||||
new RedisPubSub({
|
||||
connection:
|
||||
process.env.REDIS_PUBSUB_URL ?? 'redis://10.0.27.1:6379/7',
|
||||
}),
|
||||
},
|
||||
],
|
||||
exports: [
|
||||
Builder,
|
||||
PrismaCrudGenerator,
|
||||
GraphqlService,
|
||||
RedisService,
|
||||
'PUB_SUB',
|
||||
PubSubService,
|
||||
],
|
||||
exports: [Builder, PrismaCrudGenerator, GraphqlService, RedisService],
|
||||
})
|
||||
export class GraphqlModule {}
|
||||
|
||||
@@ -126,7 +126,7 @@ export class MinioService {
|
||||
// export document to docx format by get all pages and convert to docx
|
||||
async exportDocument(id: string) {
|
||||
// get all pages
|
||||
const pages = await this.minioClient.listObjects(
|
||||
const _pages = await this.minioClient.listObjects(
|
||||
this.configService.get('BUCKET_NAME') ?? 'epess',
|
||||
`documents/${id}`,
|
||||
)
|
||||
|
||||
9
src/Notification/notification.module.ts
Normal file
9
src/Notification/notification.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { NotificationService } from './notification.service'
|
||||
import { PubSubModule } from 'src/PubSub/pubsub.module'
|
||||
@Module({
|
||||
imports: [PubSubModule],
|
||||
providers: [NotificationService],
|
||||
exports: [NotificationService],
|
||||
})
|
||||
export class NotificationModule {}
|
||||
19
src/Notification/notification.service.ts
Normal file
19
src/Notification/notification.service.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { PubSubEvent } from 'src/common/pubsub/pubsub-event'
|
||||
import { PrismaService } from 'src/Prisma/prisma.service'
|
||||
import { PubSubService } from 'src/PubSub/pubsub.service'
|
||||
|
||||
@Injectable()
|
||||
export class NotificationService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly pubSub: PubSubService,
|
||||
) {}
|
||||
|
||||
async sendNotification(userId: string, title: string, content: string) {
|
||||
await this.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${userId}`, {
|
||||
title,
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
156
src/OpenAI/Instructions/delta.mdx.txt
Normal file
156
src/OpenAI/Instructions/delta.mdx.txt
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: Delta
|
||||
---
|
||||
|
||||
Deltas are a simple, yet expressive format that can be used to describe Quill's contents and changes. The format is a strict subset of JSON, is human readable, and easily parsible by machines. Deltas can describe any Quill document, includes all text and formatting information, without the ambiguity and complexity of HTML.
|
||||
|
||||
<Hint>
|
||||
Don't be confused by its name <em>Delta</em>—Deltas represents both documents and changes to documents. If you think of Deltas as the instructions from going from one document to another, the way Deltas represent a document is by expressing the instructions starting from an empty document.
|
||||
</Hint>
|
||||
|
||||
Deltas are implemented as a separate [standalone library](https://github.com/quilljs/delta/), allowing its use outside of Quill. It is suitable for [Operational Transform](https://en.wikipedia.org/wiki/Operational_transformation) and can be used in realtime, Google Docs like applications. For a more in depth explanation behind Deltas, see [Designing the Delta Format](/guides/designing-the-delta-format/).
|
||||
|
||||
<Hint>
|
||||
It is not recommended to construct Deltas by hand—rather use the chainable [`insert()`](https://github.com/quilljs/delta#insert), [`delete()`](https://github.com/quilljs/delta#delete), and [`retain()`](https://github.com/quilljs/delta#retain) methods to create new Deltas. You can use [`import()`](/docs/api/#import) to access Delta from Quill.
|
||||
</Hint>
|
||||
|
||||
## Document
|
||||
|
||||
The Delta format is almost entirely self-explanatory—the example below describes the string "Gandalf the Grey" where "Gandalf" is bolded and "Grey" is colored #cccccc.
|
||||
|
||||
```javascript
|
||||
{
|
||||
ops: [
|
||||
{ insert: 'Gandalf', attributes: { bold: true } },
|
||||
{ insert: ' the ' },
|
||||
{ insert: 'Grey', attributes: { color: '#cccccc' } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
As its name would imply, describing content is actually a special case for Deltas. The above example is more specifically instructions to insert a bolded string "Gandalf", an unformatted string " the ", followed by the string "Grey" colored #cccccc. When Deltas are used to describe content, it can be thought of as the content that would be created if the Delta was applied to an empty document.
|
||||
|
||||
Since Deltas are a data format, there is no inherent meaning to the values of `attribute` keypairs. For example, there is nothing in the Delta format that dictates color value must be in hex—this is a choice that Quill makes, and can be modified if desired with [Parchment](https://github.com/quilljs/parchment/).
|
||||
|
||||
### Embeds
|
||||
|
||||
For non-text content such as images or formulas, the insert key can be an object. The object should have one key, which will be used to determine its type. This is the `blotName` if you are building custom content with [Parchment](https://github.com/quilljs/parchment/). Like text, embeds can still have an `attributes` key to describe formatting to be applied to the embed. All embeds have a length of one.
|
||||
|
||||
```javascript
|
||||
{
|
||||
ops: [{
|
||||
// An image link
|
||||
insert: {
|
||||
image: 'https://quilljs.com/assets/images/icon.png'
|
||||
},
|
||||
attributes: {
|
||||
link: 'https://quilljs.com'
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Line Formatting
|
||||
|
||||
Attributes associated with a newline character describes formatting for that line.
|
||||
|
||||
```javascript
|
||||
{
|
||||
ops: [
|
||||
{ insert: 'The Two Towers' },
|
||||
{ insert: '\n', attributes: { header: 1 } },
|
||||
{ insert: 'Aragorn sped on up the hill.\n' }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
All Quill documents must end with a newline character, even if there is no formatting applied to the last line. This way, you will always have a character position to apply line formatting to.
|
||||
|
||||
Many line formats are exclusive. For example Quill does not allow a line to simultaneously be both a header and a list, despite being possible to represent in the Delta format.
|
||||
|
||||
## Changes
|
||||
|
||||
When you register a listener for Quill's [`text-change`](/docs/api/#text-change) event, one of the arguments you will get is a Delta describing what changed. In addition to `insert` operations, this Delta might also have `delete` or `retain` operations.
|
||||
|
||||
### Delete
|
||||
|
||||
The `delete` operation instructs exactly what it implies: delete the next number of characters.
|
||||
|
||||
```javascript
|
||||
{
|
||||
ops: [
|
||||
{ delete: 10 } // Delete the next 10 characters
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Since `delete` operations do not include _what_ was deleted, a Delta is not reversible.
|
||||
|
||||
### Retain
|
||||
|
||||
A `retain` operation simply means keep the next number of characters, without modification. If `attributes` is specified, it still means keep those characters, but apply the formatting specified by the `attributes` object. A `null` value for an attributes key is used to specify format removal.
|
||||
|
||||
Starting with the above "Gandalf the Grey" example:
|
||||
|
||||
```javascript
|
||||
// {
|
||||
// ops: [
|
||||
// { insert: 'Gandalf', attributes: { bold: true } },
|
||||
// { insert: ' the ' },
|
||||
// { insert: 'Grey', attributes: { color: '#cccccc' } }
|
||||
// ]
|
||||
// }
|
||||
|
||||
{
|
||||
ops: [
|
||||
// Unbold and italicize "Gandalf"
|
||||
{ retain: 7, attributes: { bold: null, italic: true } },
|
||||
|
||||
// Keep " the " as is
|
||||
{ retain: 5 },
|
||||
|
||||
// Insert "White" formatted with color #fff
|
||||
{ insert: 'White', attributes: { color: '#fff' } },
|
||||
|
||||
// Delete "Grey"
|
||||
{ delete: 4 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Note that a Delta's instructions always starts at the beginning of the document. And because of plain `retain` operations, we never need to specify an index for a `delete` or `insert` operation.
|
||||
|
||||
### Playground
|
||||
|
||||
Play around with Quill and take a look at how its content and changes look. Open your developer console for another view into the Deltas.
|
||||
|
||||
<SandpackWithQuillTemplate
|
||||
preferPreview
|
||||
afterEditor={`
|
||||
<pre id="playground" style="font-size: 12px">
|
||||
</pre>
|
||||
`}
|
||||
files={{
|
||||
'index.js': `
|
||||
const quill = new Quill('#editor', { theme: 'snow' });
|
||||
|
||||
quill.on(Quill.events.TEXT_CHANGE, update);
|
||||
const playground = document.querySelector('#playground');
|
||||
update();
|
||||
|
||||
function formatDelta(delta) {
|
||||
return \`<div>\${JSON.stringify(delta.ops, null, 2)}</div>\`;
|
||||
}
|
||||
|
||||
function update(delta) {
|
||||
const contents = quill.getContents();
|
||||
let html = \`<h3>contents</h3>\${formatDelta(contents)}\`
|
||||
if (delta) {
|
||||
html = \`\${html}<h3>change</h3>\${formatDelta(delta)}\`;
|
||||
}
|
||||
playground.innerHTML = html;
|
||||
}
|
||||
|
||||
`
|
||||
}}
|
||||
/>
|
||||
@@ -7,6 +7,7 @@ const openaiOptions: ClientOptions = {
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
baseURL: process.env.OPENAI_BASE_URL,
|
||||
maxRetries: parseInt(process.env.OPENAI_MAX_RETRIES as string) ?? 3,
|
||||
dangerouslyAllowBrowser: true,
|
||||
}
|
||||
|
||||
@Module({
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Injectable, Logger } from '@nestjs/common'
|
||||
import { OpenAI } from 'openai'
|
||||
import { DocumentDelta } from 'src/Document/document.type'
|
||||
import Delta from 'quill-delta'
|
||||
import { z } from 'zod'
|
||||
import { zodResponseFormat } from 'openai/helpers/zod'
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
const DELTA_INSTRUCTIONS = readFileSync('./src/OpenAI/Instructions/delta.mdx.txt', 'utf8')
|
||||
|
||||
@Injectable()
|
||||
export class OpenaiService {
|
||||
@@ -19,16 +24,173 @@ export class OpenaiService {
|
||||
return response.choices[0].message.content
|
||||
}
|
||||
|
||||
async documentSuggestEditDelta(documentDelta: DocumentDelta): Promise<DocumentDelta> {
|
||||
const prompt = `
|
||||
give me suggestion for edit document delta to EPESS and replace {{ documentDelta }} with ${documentDelta}
|
||||
`
|
||||
async documentSuggestEditDelta(documentDelta: Delta): Promise<Delta> {
|
||||
// Redefine the schema for Delta.Op with more detailed validations
|
||||
const deltaOpSchema = z
|
||||
.object({
|
||||
insert: z.string().optional(),
|
||||
delete: z.number().optional(),
|
||||
retain: z.number().optional(),
|
||||
attributes: z
|
||||
.object({
|
||||
bold: z.boolean().optional(),
|
||||
italic: z.boolean().optional(),
|
||||
color: z.string().optional(),
|
||||
link: z.string().optional(),
|
||||
header: z.number().optional(),
|
||||
// Add any other attributes that may be required here
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
const response = await this.openai.chat.completions.create({
|
||||
model: 'gpt-4o',
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
// Define the overall schema for Delta operations
|
||||
const schema = z.object({
|
||||
ops: z.array(deltaOpSchema),
|
||||
})
|
||||
|
||||
return response.choices[0].message.content as unknown as DocumentDelta
|
||||
const systemPrompt = `
|
||||
System: You are an assistant tasked with editing document content in QuillJS Delta format. You will receive a document and editing instructions. Your goal is to modify the document while maintaining the exact **language**, **tone**, **structure**, and **intent** of the original content. Every modification must be consistent with the original style. Please follow the instructions meticulously and ensure all operations are precisely represented in Delta format.
|
||||
|
||||
1. **Document Content (Delta Format):**
|
||||
- The document content is provided in QuillJS Delta format. This is the **original** document content. Do not alter or change the language or tone of the original content unless specifically instructed.
|
||||
|
||||
---BEGIN---
|
||||
(Insert the original document content in Delta format here.)
|
||||
---END---
|
||||
|
||||
2. **Editing Instructions (Delta Format):**
|
||||
- Below are the instructions for editing the document. Follow these instructions strictly and ensure that changes are applied precisely as requested. Do not introduce new meanings or alter the context of the original document.
|
||||
|
||||
---BEGIN---
|
||||
(Insert detailed instructions here, specifying what text should be modified, added, deleted, or reformatted.)
|
||||
---END---
|
||||
|
||||
3. **Editing Requirements:**
|
||||
- **Language Consistency**: The document's language must remain consistent throughout. Do **not** introduce new phrasing, terms, or language that deviates from the original document's style or tone.
|
||||
- **Preserve the Intent**: Ensure that the intended meaning of the document is maintained. If the instructions suggest changes, they must fit within the overall context and tone of the original.
|
||||
- **Formatting and Structure**: Respect the original formatting and structural layout. Any modifications should adhere to the same format as the source document.
|
||||
- **Modification Representation**: All changes should be accurately reflected in **Delta format**, which includes:
|
||||
- **Insert**: For adding new text or content (e.g., images, links).
|
||||
- **Delete**: For removing content.
|
||||
- **Retain**: For keeping content but potentially modifying formatting or attributes.
|
||||
|
||||
4. **Web Browsing and Reference Suggestions:**
|
||||
- If applicable, suggest **web references** that are directly relevant to the content. If web browsing is required, ensure the reference links are **reliable**, **valid**, and fit seamlessly within the document's language.
|
||||
- When suggesting external links, make sure they are:
|
||||
- **Relevant** to the document's context.
|
||||
- **Formatted correctly** with proper citations.
|
||||
|
||||
5. **Delta Operations (Insert, Delete, Retain):**
|
||||
- Ensure that all modifications are represented by the appropriate **Delta operations**:
|
||||
- **Insert**: Add new content (text, images, etc.).
|
||||
- **Delete**: Remove content.
|
||||
- **Retain**: Keep content with no change, or only adjust its attributes.
|
||||
- All modifications should be **accurately** expressed using these operations.
|
||||
|
||||
6. **Handling Non-Text Content (Embeds):**
|
||||
- If there are changes to non-text elements (such as images, links, or videos), ensure they are added correctly using the **insert** operation with appropriate attributes (e.g., \`link\`, \`image\`).
|
||||
- Example:
|
||||
\`\`\`json
|
||||
{
|
||||
"ops": [
|
||||
{
|
||||
"insert": { "image": "https://example.com/image.png" },
|
||||
"attributes": { "link": "https://example.com" }
|
||||
}
|
||||
]
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
7. **Line and Paragraph Formatting:**
|
||||
- If changes involve formatting at the line or paragraph level (e.g., headings, lists, indentation), apply the appropriate **attributes**:
|
||||
- Example for adding a heading:
|
||||
\`\`\`json
|
||||
{
|
||||
"ops": [
|
||||
{ "insert": "New Heading" },
|
||||
{ "insert": "\\n", "attributes": { "header": 2 } }
|
||||
]
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
8. **Character Count and Delta Operations:**
|
||||
- Ensure that **Delta operations** correctly represent the changes to text length. Track the **character count** and ensure the Delta format reflects both the original and modified content accurately.
|
||||
|
||||
9. **Final Output:**
|
||||
- The **final output** must be a valid QuillJS Delta format document that:
|
||||
- Contains the necessary Delta operations (insert, delete, retain).
|
||||
- Reflects all modifications clearly and precisely, without altering the document’s original tone, intent, or structure.
|
||||
|
||||
10. **Additional Instructions for Handling Complex Edits:**
|
||||
- If the document requires complex edits (e.g., restructuring, replacing long sections of text), break the edits into smaller steps and ensure each step follows the same language and tone.
|
||||
|
||||
|
||||
|
||||
`
|
||||
const userPrompt = `
|
||||
User: I need you to edit the following document in QuillJS Delta format according to the instructions below.
|
||||
|
||||
1. **Document Content (Delta Format):**
|
||||
- Here is the document content in QuillJS Delta format. This is the **original** content of the document, and it must be preserved in **language, tone, and structure** unless specifically instructed otherwise.
|
||||
|
||||
---BEGIN---
|
||||
${JSON.stringify(documentDelta)}
|
||||
---END---
|
||||
|
||||
2. **Editing Instructions (Delta Format):**
|
||||
- Below are the detailed instructions for editing the content. Follow these instructions exactly as provided, ensuring that any changes made are in line with the original language and tone of the document.
|
||||
|
||||
---BEGIN---
|
||||
${DELTA_INSTRUCTIONS}
|
||||
---END---
|
||||
|
||||
3. **Requirements:**
|
||||
- **Language and Tone**: Do not deviate from the original language or tone. Any changes should be phrased in the same style and maintain the same level of formality, diction, and style.
|
||||
- **Preserve Intent**: Ensure that all edits preserve the original intent and meaning of the document. Avoid any rephrasing or changes that alter the message.
|
||||
- **Accurate Delta Representation**: Ensure all changes are accurately reflected in **Delta format**:
|
||||
- **Insert**: For new content.
|
||||
- **Delete**: For removed content.
|
||||
- **Retain**: For unchanged content with potential formatting changes.
|
||||
|
||||
4. **Web Browsing and Reference Suggestions:**
|
||||
- If needed, suggest **web references** that are relevant to the document's context. Only insert valid URLs with descriptions that complement the document's language and tone.
|
||||
|
||||
5. **Output:**
|
||||
- Provide the final **Delta format output** that accurately reflects all changes, including inserts, deletes, and retains, while maintaining consistency in language and tone.
|
||||
|
||||
6. **Delta Format Schema:**
|
||||
- Ensure the Delta format adheres to the following structure:
|
||||
- **Insert**: For adding new content (text, links, images, etc.).
|
||||
- **Delete**: For removing content.
|
||||
- **Retain**: For keeping content, but possibly modifying formatting or attributes.
|
||||
|
||||
7. **Handling Non-Text Embeds:**
|
||||
- For any non-text content, such as **images** or **videos**, use the appropriate **insert** operation with attributes. Ensure that any media links are formatted correctly and are relevant to the document’s content.
|
||||
`
|
||||
Logger.log(userPrompt, 'userPrompt')
|
||||
|
||||
try {
|
||||
const response = await this.openai.chat.completions.create({
|
||||
model: 'gpt-4o',
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt },
|
||||
],
|
||||
response_format: zodResponseFormat(schema, 'Delta'),
|
||||
})
|
||||
|
||||
const result = JSON.parse(response.choices[0].message.content ?? '') as Delta
|
||||
|
||||
// Validate that the result matches the expected structure
|
||||
schema.parse(result)
|
||||
|
||||
Logger.log(result, 'result')
|
||||
return result
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
} catch (error: any) {
|
||||
Logger.error(`Error in Delta processing: ${error.message}`, 'OpenaiService')
|
||||
throw new Error('Failed to process Delta changes.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
src/PubSub/pubsub.module.ts
Normal file
19
src/PubSub/pubsub.module.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Global, Module } from '@nestjs/common'
|
||||
import { PubSubService } from './pubsub.service'
|
||||
import { RedisPubSub, PubSubRedisOptions } from 'graphql-redis-subscriptions'
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
PubSubService,
|
||||
{
|
||||
provide: 'PUB_SUB_REDIS',
|
||||
useFactory: () =>
|
||||
new RedisPubSub({
|
||||
connection: process.env.REDIS_PUBSUB_URL ?? 'redis://10.0.27.1:6379/7',
|
||||
}),
|
||||
},
|
||||
],
|
||||
exports: [PubSubService, 'PUB_SUB_REDIS'],
|
||||
})
|
||||
export class PubSubModule {}
|
||||
13
src/PubSub/pubsub.service.ts
Normal file
13
src/PubSub/pubsub.service.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Inject, Injectable } from '@nestjs/common'
|
||||
import { RedisPubSub } from 'graphql-redis-subscriptions'
|
||||
|
||||
@Injectable()
|
||||
export class PubSubService {
|
||||
constructor(@Inject('PUB_SUB_REDIS') private readonly pubSub: RedisPubSub) {}
|
||||
|
||||
// publish a message to a channel
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
async publish(channel: string, payload: any) {
|
||||
await this.pubSub.publish(channel, payload)
|
||||
}
|
||||
}
|
||||
@@ -546,6 +546,7 @@ export class UserSchema extends PothosSchema {
|
||||
} = ctx
|
||||
return pubSub.asyncIterator([
|
||||
`${PubSubEvent.NEW_MESSAGE}.${ctx.websocket.me?.id}`,
|
||||
`${PubSubEvent.NOTIFICATION}.${ctx.websocket.me?.id}`,
|
||||
]) as unknown as AsyncIterable<Message>
|
||||
},
|
||||
resolve: async (payload: Message) => payload,
|
||||
|
||||
@@ -59,6 +59,10 @@ export class DateTimeUtils {
|
||||
return false
|
||||
}
|
||||
|
||||
static format(dateTime: DateTime, format: string): string {
|
||||
return dateTime.toFormat(format)
|
||||
}
|
||||
|
||||
static fromIsoString(isoString: string): DateTime {
|
||||
const dateTime = DateTime.fromISO(isoString)
|
||||
if (!dateTime.isValid) {
|
||||
|
||||
Reference in New Issue
Block a user