Skip to content

Commit

Permalink
Merge pull request #276 from priyanshuverma-dev/feat-tth-magic
Browse files Browse the repository at this point in the history
feat: Text-to-handwriting Magic Tool added
  • Loading branch information
kom-senapati authored Nov 6, 2024
2 parents 02a7803 + d71c191 commit 18bb435
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
/tailwind/*
*.log
/temp_audio/*
app/temp_audio/*
app/temp_audio/*
app/temp_pdfs/*
74 changes: 73 additions & 1 deletion app/api_routes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from flask import Flask, Blueprint, request, jsonify, session, Response, send_file
from fpdf import FPDF
import re
import os
import uuid
from sqlalchemy import func
from .models import User, Chatbot, Chat, Image, Comment, ChatbotVersion
from sqlalchemy.exc import IntegrityError
Expand Down Expand Up @@ -792,7 +794,6 @@ def api_tts():
return jsonify({"success": False, "message": "Text not found"}), 400

filepath = text_to_mp3(text)
print(filepath)

response = send_file(filepath, as_attachment=True)

Expand Down Expand Up @@ -850,3 +851,74 @@ def api_ocr():

except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500


class HandwrittenPDF(FPDF):
def header(self):
pass

def footer(self):
pass

def add_custom_font(self, font_name, font_path):
self.add_font(font_name, "", font_path, uni=True)


@api_bp.route("/api/tth", methods=["POST"])
@jwt_required()
def api_tth():
try:
data = request.get_json()
text = data.get("text", "")
font_size = data.get("font_size", 12)
pdf = HandwrittenPDF()

custom_font_name = "Handwritten" # Font identifier
font_path = os.path.join(os.path.dirname(__file__), "fonts", "handwriting.ttf")
pdf.add_custom_font(custom_font_name, font_path)

pdf.add_page()
pdf.set_font(custom_font_name, size=font_size) # Use the custom font
pdf.set_text_color(0, 0, 255) # Blue ink color

# Text formatting
line_height = font_size * 0.9
margin = 10
page_width = pdf.w - 2 * margin
pdf.set_left_margin(margin)
pdf.set_right_margin(margin)

# Wrap text to fit within the page width
lines = text.split("\n")
for line in lines:
words = line.split()
current_line = ""
for word in words:
test_line = f"{current_line} {word}".strip()
if pdf.get_string_width(test_line) <= page_width:
current_line = test_line
else:
pdf.cell(0, line_height, current_line, ln=True)
current_line = word
if current_line:
pdf.cell(0, line_height, current_line, ln=True)
pdf.ln(line_height * 0.2) # Add extra line space after each paragraph

base_path = os.path.dirname(
os.path.abspath(__file__)
) # Get the absolute path of the script
temp_dir = os.path.join(base_path, "temp_pdfs")
os.makedirs(temp_dir, exist_ok=True)
# Save the PDF
output_path = f"{temp_dir}/{uuid.uuid4()}.pdf"
pdf.output(output_path)

response = send_file(output_path, as_attachment=True)

@response.call_on_close
def remove_file():
os.remove(output_path)

return response
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
Binary file added app/fonts/handwriting.pkl
Binary file not shown.
Binary file added app/fonts/handwriting.ttf
Binary file not shown.
Binary file modified client/bun.lockb
Binary file not shown.
7 changes: 7 additions & 0 deletions client/src/components/modals/command-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
Image,
Languages,
PanelTopInactive,
PenLineIcon,
Plus,
TextCursorInput,
} from "lucide-react";
Expand All @@ -28,6 +29,7 @@ import {
useOcrMagic,
useSettingsModal,
useTranslateMagicModal,
usettHMagic,
useTtsMagicModal,
} from "@/stores/modal-store";
import { useNavigate } from "react-router-dom";
Expand All @@ -41,6 +43,7 @@ export function CommandModal() {
const imagineModal = useImagineModal();
const ttsModal = useTtsMagicModal();
const ocrModal = useOcrMagic();
const ttHModal = usettHMagic();
const translateModal = useTranslateMagicModal();
const navigate = useNavigate();

Expand Down Expand Up @@ -79,6 +82,10 @@ export function CommandModal() {
<TextCursorInput />
<span>Text Extractor (OCR)</span>
</CommandItem>
<CommandItem onSelect={() => ttHModal.onOpen({ text: "" })}>
<PenLineIcon />
<span>Text To Handwriting</span>
</CommandItem>
<CommandItem onSelect={() => imagineModal.onOpen()}>
<Image />
<span>{t("commandbox.image_generation")}</span>
Expand Down
136 changes: 136 additions & 0 deletions client/src/components/modals/ttH-magic-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {
AlertDialog,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { usettHMagic } from "@/stores/modal-store";
import { useEffect, useState } from "react";
import { Button } from "../ui/button";
import toast from "react-hot-toast";
import { Textarea } from "../ui/textarea";
import { Download, X } from "lucide-react";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import axios from "axios";
import { SERVER_URL } from "@/lib/utils";

const markdownToPlainText = (markdown: string) => {
return markdown
.replace(/(\*\*|__)(.*?)\1/g, "$2") // Bold
.replace(/(\*|_)(.*?)\1/g, "$2") // Italics
.replace(/~~(.*?)~~/g, "$1") // Strikethrough
.replace(/`{1,2}(.*?)`{1,2}/g, "$1") // Inline code
.replace(/### (.*?)\n/g, "$1\n") // H3
.replace(/## (.*?)\n/g, "$1\n") // H2
.replace(/# (.*?)\n/g, "$1\n") // H1
.replace(/>\s?(.*?)(?=\n|$)/g, "$1") // Blockquote
.replace(/^\s*\n/g, "") // Remove empty lines
.replace(/\n+/g, "\n") // Consolidate newlines
.trim(); // Trim whitespace
};

export default function TtHMagicModal() {
const modal = usettHMagic();
const initialText = markdownToPlainText(modal.extras.text || "");
const [text, setText] = useState("");
const [loading, setLoading] = useState(false);
const [fontSize, setFontSize] = useState(12);

// Set initial text when the modal opens
useEffect(() => {
if (modal.isOpen) {
setText(initialText); // Set the initial text from modal extras
}
}, [modal.isOpen, initialText]); // Depend on modal open state and initial text

// Function to download as PDF
const downloadAsPDF = async () => {
setLoading(true);
try {
const token = localStorage.getItem("token");

const authHeaders = {
Authorization: `Bearer ${token || ""}`,
};

const response = await axios.post(
`${SERVER_URL}/api/tth`,
{ text, font_size: 12 },
{ responseType: "blob", headers: authHeaders } // Important to handle binary data
);

const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "handwritten_notes.pdf");
document.body.appendChild(link);
link.click();
link.remove();
toast.success("PDF created successfully!");
} catch (error) {
console.log("Error creating PDF:", error);
toast.error("Failed to create PDF.");
} finally {
setLoading(false);
}
};

return (
<AlertDialog open={modal.isOpen} onOpenChange={() => modal.onClose()}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
<div className="flex items-center justify-between">
<p>Text-to-Handwriting Magic</p>
<Button
variant={"outline"}
size={"icon"}
className="rounded-full"
onClick={() => modal.onClose()}
>
<X />
</Button>
</div>
</AlertDialogTitle>
<AlertDialogDescription>
Convert Text to Hand written Notes
</AlertDialogDescription>
<div className="my-4">
<Textarea
disabled={loading}
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type your text here"
rows={5}
className="w-full p-2 border rounded"
/>
</div>
<div>
<Label>Font Size: </Label>
<Input
type="number"
value={fontSize}
onChange={(e) => setFontSize(parseInt(e.target.value))}
min="16"
max="72"
step="1"
/>
</div>
</AlertDialogHeader>
<AlertDialogFooter>
<Button
disabled={loading}
onClick={downloadAsPDF}
className="w-full"
variant={"outline"}
>
<Download /> {loading ? "Baking" : "Bake"}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
2 changes: 2 additions & 0 deletions client/src/contexts/modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import OcrMagicModal from "@/components/modals/ocr-magic-modal";
import SettingsModal from "@/components/modals/settings-modal";
import ShareModal from "@/components/modals/share-modal";
import TranslateMagicModal from "@/components/modals/translate-magic-modal";
import TtHMagicModal from "@/components/modals/ttH-magic-modal";
import TtsMagicModal from "@/components/modals/Tts-magic-modal";
import UpdateChatbotModal from "@/components/modals/update-chatbot-modal";
import UpdateProfileModal from "@/components/modals/update-profile-modal";
Expand All @@ -18,6 +19,7 @@ export default function Modals() {
<SettingsModal />
<ShareModal />
<OcrMagicModal />
<TtHMagicModal />
<TtsMagicModal />
<TranslateMagicModal />
<ImagineModal />
Expand Down
2 changes: 1 addition & 1 deletion client/src/index.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");

@import url("https://fonts.googleapis.com/css2?family=Reenie+Beanie&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
Expand Down
11 changes: 11 additions & 0 deletions client/src/pages/Chatbot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { useSettings } from "@/contexts/settings-context";
import {
useSettingsModal,
useTranslateMagicModal,
usettHMagic,
useTtsMagicModal,
} from "@/stores/modal-store";
import {
Expand Down Expand Up @@ -63,6 +64,7 @@ export default function ChatbotPage() {
const messageEl = useRef<HTMLDivElement | null>(null);
const settingsModal = useSettingsModal();
const ttsMagicModal = useTtsMagicModal();
const ttHMagicModal = usettHMagic();
const translateMagicModal = useTranslateMagicModal();
const { currentConfig } = useSettings();
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -325,6 +327,15 @@ export default function ChatbotPage() {
>
{t("chatbot_page.listen")}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
ttHMagicModal.onOpen({
text: chat.response,
})
}
>
Handwriting
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand Down
1 change: 1 addition & 0 deletions client/src/stores/modal-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export const useTtsMagicModal = create<DefaultModal>(defaultModalValues);
export const useTranslateMagicModal = create<DefaultModal>(defaultModalValues);
export const useImagineModal = create<DefaultModal>(defaultModalValues);
export const useOcrMagic = create<DefaultModal>(defaultModalValues);
export const usettHMagic = create<DefaultModal>(defaultModalValues);

0 comments on commit 18bb435

Please sign in to comment.