diff --git a/src/common/groq.seeder.ts b/src/common/groq.seeder.ts new file mode 100644 index 0000000..b9be92c --- /dev/null +++ b/src/common/groq.seeder.ts @@ -0,0 +1,46 @@ +import { Groq } from "groq-sdk"; + +export async function retryOperation( + operation: () => Promise, + retries = 30, +) { + for (let i = 0; i < retries; i++) { + try { + return await operation(); + } catch (error) { + if (i === retries - 1) throw error; + await new Promise((resolve) => setTimeout(resolve, 1000)); + console.log(`Retry ${i + 1}/${retries}`); + } + } +} + +export const generateSampleData = async (prompt: string) => { + const groq = new Groq({ + apiKey: process.env["GROQ_KEY"], + }); + + const chatCompletion = await groq.chat.completions.create({ + messages: [ + { + role: "user", + content: prompt, + }, + ], + response_format: { type: "json_object" }, + model: "llama3-8b-8192", + }); + + // Parse the response + const result = chatCompletion.choices[0].message.content; + + let json; + try { + json = JSON.parse(result!); + } catch (error) { + throw new Error("Failed to parse response as JSON"); + } + + console.log(json); + return json; +}; diff --git a/src/module/order/order.repository.ts b/src/module/order/order.repository.ts index d9d016d..fdfd041 100644 --- a/src/module/order/order.repository.ts +++ b/src/module/order/order.repository.ts @@ -90,6 +90,7 @@ export class OrderRepository { const query = this.db .select({ id: orders.id, + name: tutoriesTable.name, status: orders.status, sessionTime: orders.sessionTime, estimatedEndTime: orders.estimatedEndTime, diff --git a/src/module/review/review.seeder.ts b/src/module/review/review.seeder.ts index 3b5cd55..8729524 100644 --- a/src/module/review/review.seeder.ts +++ b/src/module/review/review.seeder.ts @@ -1,10 +1,20 @@ +import { generateSampleData, retryOperation } from "@/common/groq.seeder"; import { container } from "@/container"; import { faker } from "@faker-js/faker"; const orderRepository = container.orderRepository; const reviewRepository = container.reviewRepository; -export const seedReviews = async () => { +function prompt(rating: number, category: string, title: string) { + return `You are a learner that is learning category ${category}. +You are learning from a tutor about ${title}, your rating is ${rating} out of 5. Explain your experience with the tutor. +The JSON schema should include +{ + "message": "string (min 10 characters, max 1000 characters)", +}`; +} + +export const seedReviews = async ({ generateWithGroq = false }) => { const orders = await orderRepository.getOrders({}); if (orders.length === 0) { @@ -13,10 +23,27 @@ export const seedReviews = async () => { console.log(`Seeding reviews with ${orders.length} data...`); for (let i = 0; i < orders.length; i++) { + const rating = faker.number.int({ min: 1, max: 5 }); + const { message } = generateWithGroq + ? await retryOperation(async () => { + const result = await generateSampleData( + prompt(rating, orders[i].categoryName, orders[i].name), + ); + + if (result.message.length < 10 || result.message.length > 1000) { + throw new Error( + "Message length must be between 10 and 1000 characters", + ); + } + + return result; + }) + : faker.lorem.sentence(); + await reviewRepository.createReview({ orderId: orders[i].id, - rating: faker.number.int({ min: 1, max: 5 }), - message: faker.lorem.sentence(), + rating, + message, }); } }; diff --git a/src/module/tutories/tutories.seeder.ts b/src/module/tutories/tutories.seeder.ts index 430e6a7..f9c5ff1 100644 --- a/src/module/tutories/tutories.seeder.ts +++ b/src/module/tutories/tutories.seeder.ts @@ -1,58 +1,22 @@ +import { generateSampleData, retryOperation } from "@/common/groq.seeder"; import { container } from "@/container"; import { Tutories } from "@/types"; import { faker } from "@faker-js/faker"; -import Groq from "groq-sdk"; const categoryRepository = container.categoryRepository; const tutorRepository = container.tutorRepository; const tutoriesRepository = container.tutoriesRepository; -async function retryOperation(operation: () => Promise, retries = 10) { - for (let i = 0; i < retries; i++) { - try { - return await operation(); - } catch (error) { - if (i === retries - 1) throw error; - await new Promise((resolve) => setTimeout(resolve, 1000 * i)); - console.log(`Retry ${i + 1}/${retries}`); - } - } -} - -const prompt = (category: string) => { - return `You are a tutor that is teaching category ${category}. The name is the title/headline on what specific topic you teach, keep it short and simple but descriptive. The JSON schema should include +const prompt = (tutorName: string, category: string) => { + return `You are a tutor. Your name is ${tutorName} that is teaching category ${category}. You need to make title/headline on what specific topic you teach, keep it very short and simple but descriptive. +For aboutYou, you should also tell about yourself in that particular topic, what makes you unique, what is your experience, and why should someone choose you. +The JSON schema should include { - "name": "string (max 30 characters)", + "title": "string (max 30 characters)", + "aboutYou": "string (min 10 characters, max 1000 characters)", "teachingMethodology": "string (must be detailed, min 10 characters, max 1000 characters)", }`; }; -const generateNameAndTeachingMethod = async (categoryName: string) => { - const groq = new Groq({ - apiKey: process.env["GROQ_KEY"], - }); - const chatCompletion = await groq.chat.completions.create({ - messages: [ - { - role: "user", - content: prompt(categoryName), - }, - ], - response_format: { type: "json_object" }, - model: "llama3-8b-8192", - }); - - const result = chatCompletion.choices[0].message.content; - const json = JSON.parse(result!); - if (!json || !json.name || !json.teachingMethodology) - throw new Error("Result is empty"); - - console.log(json.name); - if (json.name.length > 30) { - throw new Error("Name should be less than 30 characters"); - } - - return json; -}; export const seedTutories = async ({ generateWithGroq = false }) => { const [categoryExists, tutorsExists] = await Promise.all([ @@ -64,7 +28,6 @@ export const seedTutories = async ({ generateWithGroq = false }) => { throw new Error("Tutors or categories not found"); } - // Fetch required data const tutors = await tutorRepository.getAllTutors(); const tutories: Tutories[] = []; @@ -76,21 +39,45 @@ export const seedTutories = async ({ generateWithGroq = false }) => { ); const randomCategory = faker.helpers.arrayElement(categories); - const { name, teachingMethodology } = generateWithGroq - ? await retryOperation(() => - generateNameAndTeachingMethod(randomCategory.name), - ) + const { title, aboutYou, teachingMethodology } = generateWithGroq + ? await retryOperation(async () => { + const result = await generateSampleData( + prompt(tutor.name, randomCategory.name), + ); + + if (result.title.length > 30) { + throw new Error("Title length exceeds 30 characters"); + } + + if (result.aboutYou.length < 10 || result.aboutYou.length > 1000) { + throw new Error( + "AboutYou length should be between 10 and 1000 characters", + ); + } + + if ( + result.teachingMethodology.length < 10 || + result.teachingMethodology.length > 1000 + ) { + throw new Error( + "TeachingMethodology length should be between 10 and 1000 characters", + ); + } + + return result; + }) : { - name: faker.lorem.words({ min: 1, max: 2 }), + title: faker.lorem.words({ min: 1, max: 2 }), + aboutYou: faker.lorem.paragraph(), teachingMethodology: faker.lorem.paragraph(), }; tutories.push({ - name, + name: title, tutorId: tutor.id, categoryId: randomCategory.id, createdAt: new Date(), - aboutYou: faker.lorem.paragraph(), + aboutYou, teachingMethodology, hourlyRate: faker.helpers.arrayElement([50000, 100000, 150000, 200000]), typeLesson: faker.helpers.arrayElement(["online", "offline", "both"]), diff --git a/src/seed.ts b/src/seed.ts index e4879ee..91c46cc 100644 --- a/src/seed.ts +++ b/src/seed.ts @@ -16,7 +16,7 @@ export const runSeeder = async () => { await seedTutories({ generateWithGroq: true }); await assignTutorProfilePictures(); await seedOrders(); - await seedReviews(); + await seedReviews({ generateWithGroq: true }); }; runSeeder();