diff --git a/penparse/penparse/settings.py b/penparse/penparse/settings.py index 9324665..3f1882c 100644 --- a/penparse/penparse/settings.py +++ b/penparse/penparse/settings.py @@ -43,7 +43,8 @@ INSTALLED_APPS = [ "django.contrib.messages", "django.contrib.staticfiles", "webui", - "markdown_deux" + "markdown_deux", + "markdownify.apps.MarkdownifyConfig", ] MIDDLEWARE = [ @@ -74,6 +75,15 @@ TEMPLATES = [ }, ] +MARKDOWNIFY = { + "default": { + "MARKDOWN_EXTENSIONS": [ + "markdown.extensions.fenced_code", # dotted path + "fenced_code", # also works + ] + } +} + WSGI_APPLICATION = "penparse.wsgi.application" diff --git a/penparse/webui/tasks.py b/penparse/webui/tasks.py index 934b068..0c39599 100644 --- a/penparse/webui/tasks.py +++ b/penparse/webui/tasks.py @@ -11,13 +11,13 @@ from django.conf import settings from .models import ImageMemo, MemoStatus from datetime import datetime -TRANSCRIBE_PROMPT = """Transcribe the hand written notes in the attached image and present them as markdown inside a fence like so +TRANSCRIBE_PROMPT = """Transcribe the hand written notes in the attached image and present them as markdown. -```markdown - -``` +Do not use a fence, simply respond using markdown. -If any words or letters are unclear, denote them with a '??'. For example if you were not sure whether a word is blow or blew you would transcribe it as '?blow?' +If any words or letters are unclear, denote them with a '??'. + +For example if you were not sure whether a word is blow or blew you would transcribe it as '?blow?' """ diff --git a/penparse/webui/templates/dashboard.html b/penparse/webui/templates/dashboard.html index e7b7666..08ad24d 100644 --- a/penparse/webui/templates/dashboard.html +++ b/penparse/webui/templates/dashboard.html @@ -23,20 +23,26 @@ alt="{{ document.title }} thumbnail" class="w-full h-48 object-cover mb-4 rounded" /> -
-

{{ document.title }}

- - {{ document.status|title }} - -
-

- Created: {{ document.created_at }} -

-

- Last Updated: {{ document.updated_at }} -

+ + + + + + + + + + + + + +
Status: + + {{ document.status|title }} + +
Created:{{ document.created_at|date:"d/m/Y H:i" }}
Updated:{{ document.updated_at|date:"d/m/Y H:i" }}
{% if document.content %}

Content Preview:

diff --git a/penparse/webui/templates/document.html b/penparse/webui/templates/document.html new file mode 100644 index 0000000..d1ac6df --- /dev/null +++ b/penparse/webui/templates/document.html @@ -0,0 +1,181 @@ +{% extends "main.html" %} {% load markdown_deux_tags %} {% load markdownify %} +{% block content %} +
+

Document View

+
+
+
+
+

+ {{ document.title }} +

+
+ + + + + + + + + + + + + +
Status: + + {{ document.status|title }} + +
Created:{{ document.created_at|date:"d/m/Y H:i" }}
Updated:{{ document.updated_at|date:"d/m/Y H:i" }}
+
+
+
+

Content:

+ +
+
+ {{ document.content|markdown }} +
+ +
+
+ + + + + Export + +
+ {% csrf_token %} + +
+
+
+
+
+ {{ document.title }} +
+
+
+
+
+
+
+ + + + + Back to Dashboard + +
+{% endblock %} {% block extra_js %} + +{% endblock %} diff --git a/penparse/webui/templates/main.html b/penparse/webui/templates/main.html index 918f1e7..0c68313 100644 --- a/penparse/webui/templates/main.html +++ b/penparse/webui/templates/main.html @@ -5,6 +5,7 @@ PenParse - Intelligent OCR for Handwritten Notes + {% block extra_js %}{% endblock %}
{% include "partial/nav.html" %}
diff --git a/penparse/webui/urls.py b/penparse/webui/urls.py index cbe6e83..0125ef3 100644 --- a/penparse/webui/urls.py +++ b/penparse/webui/urls.py @@ -13,6 +13,11 @@ urlpatterns = [ views.document_thumbnail, name="document_thumbnail", ), + path( + "documents//image", + views.document_image, + name="document_image", + ), path( "documents//download", views.download_document, name="download_document" ), diff --git a/penparse/webui/views/__init__.py b/penparse/webui/views/__init__.py index 3c6d87f..51998f7 100644 --- a/penparse/webui/views/__init__.py +++ b/penparse/webui/views/__init__.py @@ -3,7 +3,7 @@ import os from django.contrib import messages from django.shortcuts import redirect, render -from django.http import HttpRequest +from django.http import HttpRequest, HttpResponse from django.core.files.storage import default_storage from django.core.files.base import ContentFile from ..models import ImageMemo @@ -13,7 +13,7 @@ from django.contrib.auth.decorators import login_required logger = logging.getLogger(__name__) -from .thumbnail import document_thumbnail +from .thumbnail import document_thumbnail, document_image from .register import register from .upload import upload_document from .delete import delete_document @@ -27,6 +27,7 @@ def index(request): __all__ = [ "index", "document_thumbnail", + "document_image", "register", "dashboard", "settings", @@ -54,8 +55,19 @@ def settings(request): @login_required -def view_document(request): - return render(request, "document.html") +def view_document(request: HttpRequest, pk: str): + + ## check that the document exists and belongs to the user + # find document with given ID (pk path param) and current user id + document = ImageMemo.objects.filter(id=pk, author__id=request.user.id).first() + + if not document: + logger.debug(f"No memo found for user={request.user.id} and memo_id={pk}") + return HttpResponse(content="Document not found", status=404) + + + + return render(request, "document.html", context={"document": document}) @login_required diff --git a/penparse/webui/views/thumbnail.py b/penparse/webui/views/thumbnail.py index e43d182..3f7ba5a 100644 --- a/penparse/webui/views/thumbnail.py +++ b/penparse/webui/views/thumbnail.py @@ -13,6 +13,29 @@ from django.contrib.auth.decorators import login_required from ..models import ImageMemo +@login_required +def document_image(request, pk): + """Given a document uuid, look it up, ensure that it belongs to the current user and respond with an image""" + # find document with given ID (pk path param) and current user id + document = ImageMemo.objects.filter(id=pk, author__id=request.user.id).first() + + if not document: + logger.debug(f"No memo found for user={request.user.id} and memo_id={pk}") + return HttpResponse(content="Document not found", status=404) + + # look up the file on disk + + if not default_storage.exists(document.image.name): + logger.warning( + f"The file associated with memo {document.id} does not exist" + ) + return HttpResponse(content="Document not found", status=404) + + # Return the thumbnail as an HTTP response + with default_storage.open(document.image.name,'rb') as f: + return HttpResponse(f.read(), content_type="image/jpeg") + + @login_required def document_thumbnail(request: HttpRequest, pk: str): """Given a document uuid, look it up, ensure that it belongs to the current user and respond with a thumbnail""" diff --git a/pyproject.toml b/pyproject.toml index 392e5f5..04c2b93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.9" dependencies = [ "celery>=5.4.0", "django-markdown-deux>=1.0.6", + "django-markdownify>=0.9.5", "django>=4.2.16", "litellm>=1.54.1", "loguru>=0.7.3", diff --git a/uv.lock b/uv.lock index 2d8fc9b..5077633 100644 --- a/uv.lock +++ b/uv.lock @@ -190,6 +190,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/da/43b15f28fe5f9e027b41c539abc5469052e9d48fd75f8ff094ba2a0ae767/billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb", size = 86766 }, ] +[[package]] +name = "bleach" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + [[package]] name = "celery" version = "5.4.0" @@ -393,6 +410,20 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/26/af/3ed785b661e4545709ba1618926bb33bd585d1fd2faa42a548756743e874/django-markdown-deux-1.0.6.zip", hash = "sha256:1f7b4da6b4dd1a9a84e3da90887d356f8afdd9a1e7d6468c081b8ac50a7980b1", size = 18157 } +[[package]] +name = "django-markdownify" +version = "0.9.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bleach", extra = ["css"] }, + { name = "django" }, + { name = "markdown" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/33/3abb966e2b238af4c9a5d3ee38a7aa7e51b644b4b20bf8533b6fd1c1bf96/django_markdownify-0.9.5.tar.gz", hash = "sha256:34c34eba4a797282a5c5bd97b13cec84d6a4c0673ad47ce1c1d000d74dd8d4ab", size = 7939 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/35/c7a4bd957b279a8e7c808116bed399b73874ed3da78689993ee76f30d9f6/django_markdownify-0.9.5-py3-none-any.whl", hash = "sha256:2c4ae44e386c209453caf5e9ea1b74f64535985d338ad2d5ad5e7089cc94be86", size = 10342 }, +] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -751,6 +782,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 }, ] +[[package]] +name = "markdown" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, +] + [[package]] name = "markdown2" version = "2.5.1" @@ -951,6 +994,7 @@ dependencies = [ { name = "celery" }, { name = "django" }, { name = "django-markdown-deux" }, + { name = "django-markdownify" }, { name = "litellm" }, { name = "loguru" }, { name = "pillow" }, @@ -966,6 +1010,7 @@ requires-dist = [ { name = "celery", specifier = ">=5.4.0" }, { name = "django", specifier = ">=4.2.16" }, { name = "django-markdown-deux", specifier = ">=1.0.6" }, + { name = "django-markdownify", specifier = ">=0.9.5" }, { name = "litellm", specifier = ">=1.54.1" }, { name = "loguru", specifier = ">=0.7.3" }, { name = "pillow", specifier = ">=11.0.0" }, @@ -1686,6 +1731,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/3b/7c8812952ca55e1bab08afc1dda3c5991804c71b550b9402e82a082ab795/tiktoken-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:1473cfe584252dc3fa62adceb5b1c763c1874e04511b197da4e6de51d6ce5a02", size = 884803 }, ] +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, +] + [[package]] name = "tokenizers" version = "0.21.0" @@ -1807,6 +1864,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, ] +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + [[package]] name = "win32-setctime" version = "1.2.0"