Skip to content

Commit

Permalink
Merge pull request #126 from akshat-OwO/main
Browse files Browse the repository at this point in the history
[DEPLOY]
  • Loading branch information
akshat-OwO authored Dec 30, 2024
2 parents eb8b96b + fc58043 commit 2db5f4b
Show file tree
Hide file tree
Showing 12 changed files with 457 additions and 15,649 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# syllabusx-client

## 4.10.2

### Patch Changes

- fix: mock pdf layout [#124](https://github.com/akshat-OwO/syllabusx-client/pull/124) by [@rahulpatel902](https://github.com/rahulpatel902)
- feat: claude ai models [#123](https://github.com/akshat-OwO/syllabusx-client/pull/123) by [@Shivoo29](https://github.com/Shivoo29)

## 4.10.1

### Patch Changes
Expand Down
15,383 changes: 0 additions & 15,383 deletions package-lock.json

This file was deleted.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "syllabusx-client",
"version": "4.10.1",
"version": "4.10.2",
"repository": {
"type": "git",
"url": "https://github.com/akshat-OwO/syllabusx-client.git"
Expand All @@ -16,6 +16,7 @@
"format:fix": "prettier --write ."
},
"dependencies": {
"@ai-sdk/anthropic": "^1.0.6",
"@ai-sdk/google": "^1.0.4",
"@ai-sdk/openai": "^1.0.5",
"@contentful/rich-text-react-renderer": "^15.22.7",
Expand Down Expand Up @@ -96,7 +97,7 @@
"zustand": "^4.5.4"
},
"devDependencies": {
"@changesets/cli": "^2.27.7",
"@changesets/cli": "^2.27.11",
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.3",
"@swc-jotai/react-refresh": "^0.1.1",
Expand Down
342 changes: 152 additions & 190 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions src/app/api/claude-generate-mock/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { endSemMockTemplate, midSemMockTemplate, newEndSemMockTemplate } from "@/lib/prompt";
import { AiSchema, MockPayloadSchema, MockSchema } from "@/lib/schemas";
import { createAnthropic } from "@ai-sdk/anthropic";
import { generateObject } from "ai";

export const runtime = "edge";

export async function POST(req: Request) {
const { key, model, maxMarks, semester, branch, subject, topics, type } = await req.json();

const validatedAi = AiSchema.safeParse({ key, model });

if (!validatedAi.success) return Response.json({ error: "Invalid Key" }, { status: 403 });

const validatedPayload = MockPayloadSchema.safeParse({ type, topics, semester, branch, subject });

if (!validatedPayload.success) return Response.json({ error: "Bad Request" }, { status: 400 });

const anthropic = createAnthropic({
apiKey: validatedAi.data.key,
});

try {
const { object } = await generateObject({
model: anthropic(validatedAi.data.model),
schema: MockSchema,
prompt:
validatedPayload.data.type === "midSem"
? midSemMockTemplate`${semester}${branch}${subject}${topics}`
: maxMarks === 75
? endSemMockTemplate`${semester}${branch}${subject}${topics}`
: newEndSemMockTemplate`${semester}${branch}${subject}${topics}`,
});
return Response.json(object, { status: 200 });
} catch (error) {
return Response.json({ error: "Failed to query llm model" }, { status: 500 });
}
}
22 changes: 22 additions & 0 deletions src/app/api/claude-search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AiSchema } from "@/lib/schemas";
import { createAnthropic } from "@ai-sdk/anthropic";
import { streamText } from "ai";

export const runtime = "edge";

export async function POST(req: Request) {
const { messages, key, model } = await req.json();

const validatedAI = AiSchema.safeParse({ key, model });

if (!validatedAI.success) return Response.json({ error: "Invalid key" }, { status: 403 });

const anthropic = createAnthropic({ apiKey: validatedAI.data.key });

const result = streamText({
model: anthropic(validatedAI.data.model),
messages,
});

return result.toDataStreamResponse();
}
231 changes: 184 additions & 47 deletions src/components/MockPDF.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ import { Download, Loader2 } from "lucide-react";
export const PDFDownloadButton = ({ data }: { data: TMockSchema }) => {
const generatePDF = () => {
const doc = new jsPDF();

const pageWidth = doc.internal.pageSize.getWidth();
const pageHeight = doc.internal.pageSize.getHeight();
const margin = 20;
const margin = 15;
const maxWidth = pageWidth - margin * 2;
const SPACING = {
AFTER_HEADER: 8,
AFTER_QUESTION_BOX: 4,
BETWEEN_SUBQUESTIONS: 4,
BETWEEN_QUESTIONS: 6
};

let y = margin;

// Timestamp formatting
const timestamp = new Date().toLocaleString("en-IN", {
day: "2-digit",
month: "2-digit",
Expand All @@ -25,31 +32,120 @@ export const PDFDownloadButton = ({ data }: { data: TMockSchema }) => {
hour12: false,
});

doc.setFontSize(8);
doc.setTextColor(100);
// Calculate text height for a given text
const calculateTextHeight = (text: string, fontSize: number, indent: number = 0, marksWidth: number = 0) => {
doc.setFontSize(fontSize);
const effectiveWidth = maxWidth - indent - marksWidth;
const lines = doc.splitTextToSize(text, effectiveWidth);
return Array.isArray(lines) ? lines.length * fontSize * 0.5 : fontSize * 0.5;
};

// Calculate total height for a question and its sub-questions
const calculateQuestionHeight = (question: any) => {
let height = 16 + SPACING.AFTER_QUESTION_BOX; // Question box height + spacing

question.content.forEach((subQ: any, idx: number) => {
const subQuestionText = `${String.fromCharCode(97 + idx)}) ${subQ.subQuestion}`;
const marksText = `[${subQ.marks} Marks]`;
const marksWidth = doc.getStringUnitWidth(marksText) * 11 * 0.352778 + 10;
height += calculateTextHeight(subQuestionText, 11, 8, marksWidth);
height += (idx < question.content.length - 1) ? SPACING.BETWEEN_SUBQUESTIONS : 0;
});

height += SPACING.BETWEEN_QUESTIONS;
return height;
};

const formatTitleCase = (text: string) => {
// Split by hyphens and spaces
return text.split(/[-\s]+/)
.map(word => {
// Don't capitalize certain words unless they're at the start
const lowercaseWords = ['and', 'or', 'in', 'of', 'the', 'to', 'for', 'with', 'on', 'at'];
return lowercaseWords.includes(word.toLowerCase()) ?
word.toLowerCase() :
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
})
.join(' ');
};

const watermarkText = `Generated By SyllabusX on ${timestamp}`;
const watermarkWidth =
doc.getStringUnitWidth(watermarkText) * 8 * 0.352778;
doc.text(watermarkText, pageWidth - margin - watermarkWidth - 10, margin - 10);
const addWrappedTitle = (title: string) => {
doc.setFontSize(20);
doc.setFont("helvetica", "bold");
doc.setTextColor(40, 40, 40);

// Format title to Title Case
const formattedTitle = formatTitleCase(title);

// Calculate wrapped lines with slightly reduced width for better appearance
const titleLines = doc.splitTextToSize(formattedTitle, maxWidth * 0.95);

// Add each line of the title
titleLines.forEach((line: string, index: number) => {
doc.text(line, margin, y + (index * 12));
});

// Update y position based on number of lines
y += (titleLines.length * 12);
};

doc.setTextColor(0);
const addHeader = () => {
// Top border
doc.setFillColor(40, 40, 40);
doc.rect(margin, margin, maxWidth, 0.5, 'F');
y += 10;

// Add wrapped title
addWrappedTitle(data.output.examMetadata.subject);

// Subtitle
doc.setFontSize(14);
doc.setFont("helvetica", "normal");
const examType = data.output.examMetadata.type === "midSem" ? "Mid Semester" : "End Semester";
doc.text(`${examType} Examination`, margin, y);
y += 12;

// Metadata box
doc.setDrawColor(220, 220, 220);
doc.setFillColor(250, 250, 250);
const metadataBoxHeight = 20;
doc.roundedRect(margin, y, maxWidth, metadataBoxHeight, 2, 2, 'FD');

// Metadata content
doc.setFontSize(11);
doc.setTextColor(60, 60, 60);
doc.text(`Total Marks: ${data.output.examMetadata.totalMarks}`, margin + 8, y + 8);
doc.text(`Duration: ${data.output.examMetadata.duration}`, margin + 8, y + 16);
doc.text(
`Questions to Attempt: ${data.output.examMetadata.questionsToAttempt} out of ${data.output.examMetadata.totalQuestions}`,
margin + maxWidth/2, y + 12
);

y += metadataBoxHeight + 8;
doc.setFillColor(40, 40, 40);
doc.rect(margin, y, maxWidth, 0.5, 'F');
y += SPACING.AFTER_HEADER;
};

const addWrappedText = (
text: string,
x: number,
fontSize: number = 12,
fontSize: number = 11,
isBold: boolean = false,
indent: number = 0
indent: number = 0,
color: number[] = [40, 40, 40],
marksText?: string
) => {
doc.setFontSize(fontSize);
doc.setFont("helvetica", isBold ? "bold" : "normal");
const effectiveWidth = maxWidth - indent;
doc.setTextColor(color[0], color[1], color[2]);

const marksWidth = marksText ?
doc.getStringUnitWidth(marksText) * fontSize * 0.352778 + 10 : 0;
const effectiveWidth = maxWidth - indent - marksWidth;
const textLines = doc.splitTextToSize(text, effectiveWidth);
if (y + textLines.length * (fontSize * 0.5) > pageHeight - margin) {
doc.addPage();
y = margin;
}
const startY = y;

if (Array.isArray(textLines)) {
textLines.forEach((line) => {
doc.text(line, x + indent, y);
Expand All @@ -59,34 +155,38 @@ export const PDFDownloadButton = ({ data }: { data: TMockSchema }) => {
doc.text(textLines, x + indent, y);
y += fontSize * 0.5;
}


if (marksText) {
doc.setTextColor(100, 100, 100);
doc.text(marksText, pageWidth - margin - marksWidth + 5, startY);
doc.setTextColor(color[0], color[1], color[2]);
}

return y;
};
y = addWrappedText(
`${data.output.examMetadata.subject} - ${
data.output.examMetadata.type === "midSem"
? "Mid Semester"
: "End Semester"
} Examination`,
margin,
18,
true
);
y += 10;
y = addWrappedText(
`Total Marks: ${data.output.examMetadata.totalMarks}`,
margin
);
y = addWrappedText(
`Duration: ${data.output.examMetadata.duration}`,
margin
);
y = addWrappedText(
`Questions to Attempt: ${data.output.examMetadata.questionsToAttempt} out of ${data.output.examMetadata.totalQuestions}`,
margin
);
y += 5;
data.output.questions.forEach((question) => {

const addWatermark = () => {
doc.setFontSize(8);
doc.setTextColor(130, 130, 130);
const watermarkText = `Generated By SyllabusX on ${timestamp}`;
const watermarkWidth = doc.getStringUnitWidth(watermarkText) * 8 * 0.352778;
doc.text(watermarkText, pageWidth - margin - watermarkWidth, margin - 5);
};

// Start PDF Generation
addHeader();
addWatermark();

// Questions
data.output.questions.forEach((question, index) => {
// Calculate height and check page break
const questionHeight = calculateQuestionHeight(question);
if (y + questionHeight > pageHeight - margin) {
doc.addPage();
y = margin + SPACING.AFTER_HEADER;
addWatermark(); // Add watermark to new page
}

const questionText = `Q${question.questionNumber}. ${
question.isCompulsory ? "(Compulsory) " : ""
}${
Expand All @@ -95,15 +195,52 @@ export const PDFDownloadButton = ({ data }: { data: TMockSchema }) => {
: ""
}[${question.totalMarks} Marks]`;

y = addWrappedText(questionText, margin, 12, true);
// Question box
doc.setFillColor(250, 250, 250);
doc.setDrawColor(220, 220, 220);
doc.roundedRect(margin, y - 4, maxWidth, 16, 2, 2, 'FD');

// Question text
doc.setFontSize(11);
doc.setFont("helvetica", "bold");
doc.setTextColor(40, 40, 40);
doc.text(questionText, margin + 4, y + 5);

y += 16 + SPACING.AFTER_QUESTION_BOX;

// Sub-questions
question.content.forEach((subQ, idx) => {
const subQuestionText = `${String.fromCharCode(97 + idx)}. ${subQ.subQuestion} [${subQ.marks} Marks]`;
y = addWrappedText(subQuestionText, margin, 12, false, 10);
y += 1;
// Split sub-question into label and content
const label = `${String.fromCharCode(97 + idx)})`;
const subQuestionText = `${subQ.subQuestion}`;
const marksText = `[${subQ.marks} Marks]`;

// Add bold label
doc.setFont("helvetica", "bold");
doc.text(label, margin + 8, y);

// Calculate space taken by label
const labelWidth = doc.getStringUnitWidth(label) * 11 * 0.352778;

// Add content with proper indent after label
y = addWrappedText(
subQuestionText,
margin,
11,
false,
8 + labelWidth + 4, // Add extra indent after label
[40, 40, 40],
marksText
);

// Add spacing only between sub-questions, not after the last one
if (idx < question.content.length - 1) {
y += SPACING.BETWEEN_SUBQUESTIONS;
}
});

y += 5;
// Add spacing between questions
y += SPACING.BETWEEN_QUESTIONS;
});

const filename = `${data.output.examMetadata.subject}_${
Expand Down
Loading

0 comments on commit 2db5f4b

Please sign in to comment.