implement document view
Run Tests / Run Tests (push) Successful in 38s
Details
Run Tests / Run Tests (push) Successful in 38s
Details
This commit is contained in:
parent
7b15955bf6
commit
9222739df1
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
<Content>
|
||||
```
|
||||
Do not use a fence, simply respond using markdown.
|
||||
|
||||
If any words or letters are unclear, denote them with a '?<word>?'. 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 '?<word>?'.
|
||||
|
||||
For example if you were not sure whether a word is blow or blew you would transcribe it as '?blow?'
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
@ -23,20 +23,26 @@
|
|||
alt="{{ document.title }} thumbnail"
|
||||
class="w-full h-48 object-cover mb-4 rounded"
|
||||
/>
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h3 class="text-xl font-semibold">{{ document.title }}</h3>
|
||||
<span
|
||||
class="px-2 py-1 text-xs font-semibold rounded-full {% if document.status == 'pending' %} bg-gray-200 text-gray-800 {% elif document.status == 'processing' %} bg-blue-200 text-blue-800 {% elif document.status == 'done' %} bg-green-200 text-green-800 {% elif document.status == 'error' %} bg-red-200 text-red-800 {% endif %}"
|
||||
>
|
||||
{{ document.status|title }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Created: {{ document.created_at }}
|
||||
</p>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Last Updated: {{ document.updated_at }}
|
||||
</p>
|
||||
<table class="w-full text-sm">
|
||||
<tr>
|
||||
<td class="font-medium pr-4">Status:</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-3 py-1 text-sm font-medium rounded-full {% if document.status == 'pending' %} bg-gray-200 text-gray-800 {% elif document.status == 'processing' %} bg-blue-200 text-blue-800 {% elif document.status == 'done' %} bg-green-200 text-green-800 {% elif document.status == 'error' %} bg-red-200 text-red-800 {% endif %}"
|
||||
>
|
||||
{{ document.status|title }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium pr-4">Created:</td>
|
||||
<td class="text-gray-600">{{ document.created_at|date:"d/m/Y H:i" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium pr-4">Updated:</td>
|
||||
<td class="text-gray-600">{{ document.updated_at|date:"d/m/Y H:i" }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% if document.content %}
|
||||
<div class="text-gray-700 mb-4">
|
||||
<h4 class="font-semibold mb-2">Content Preview:</h4>
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
{% extends "main.html" %} {% load markdown_deux_tags %} {% load markdownify %}
|
||||
{% block content %}
|
||||
<section class="max-w-6xl mx-auto px-4 py-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 mb-6">Document View</h1>
|
||||
<div class="bg-white shadow-md rounded-lg overflow-hidden">
|
||||
<div class="p-6">
|
||||
<div class="flex flex-col lg:flex-row gap-8">
|
||||
<div class="w-full lg:w-1/2">
|
||||
<h2 class="text-2xl font-semibold text-gray-800 mb-4">
|
||||
{{ document.title }}
|
||||
</h2>
|
||||
<div class="mb-4">
|
||||
<table class="w-full text-sm">
|
||||
<tr>
|
||||
<td class="font-medium pr-4">Status:</td>
|
||||
<td>
|
||||
<span
|
||||
class="px-3 py-1 text-sm font-medium rounded-full {% if document.status == 'pending' %} bg-gray-200 text-gray-800 {% elif document.status == 'processing' %} bg-blue-200 text-blue-800 {% elif document.status == 'done' %} bg-green-200 text-green-800 {% elif document.status == 'error' %} bg-red-200 text-red-800 {% endif %}"
|
||||
>
|
||||
{{ document.status|title }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium pr-4">Created:</td>
|
||||
<td class="text-gray-600">{{ document.created_at|date:"d/m/Y H:i" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-medium pr-4">Updated:</td>
|
||||
<td class="text-gray-600">{{ document.updated_at|date:"d/m/Y H:i" }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="prose max-w-full">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h3 class="text-xl font-semibold text-gray-800">Content:</h3>
|
||||
<button
|
||||
id="copyButton"
|
||||
class="bg-gray-200 text-gray-700 px-3 py-1 rounded-md hover:bg-gray-300 transition duration-300 flex items-center text-sm"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
Copy to Clipboard
|
||||
</button>
|
||||
</div>
|
||||
<div id="markdown-rendered" class="bg-gray-50 p-4 rounded-md">
|
||||
{{ document.content|markdown }}
|
||||
</div>
|
||||
<div id="markdown-content" class="hidden">{{ document.content}}</div>
|
||||
</div>
|
||||
<div class="mt-8 flex space-x-4">
|
||||
<a
|
||||
href="{% url 'download_document' document.id %}"
|
||||
class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 transition duration-300 flex items-center"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
></path>
|
||||
</svg>
|
||||
Export
|
||||
</a>
|
||||
<form
|
||||
action="{% url 'delete_document' document.id %}"
|
||||
method="post"
|
||||
onsubmit="return confirm('Are you sure you want to delete this document?');"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 transition duration-300 flex items-center"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
></path>
|
||||
</svg>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:w-1/2">
|
||||
<div class="bg-gray-100 rounded-lg overflow-hidden">
|
||||
<img
|
||||
src="{% url 'document_image' pk=document.id %}"
|
||||
alt="{{ document.title }}"
|
||||
class="w-full h-auto object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="max-w-6xl mx-auto px-4 py-4">
|
||||
<a
|
||||
href="{% url 'dashboard' %}"
|
||||
class="text-blue-600 hover:text-blue-800 flex items-center"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
></path>
|
||||
</svg>
|
||||
Back to Dashboard
|
||||
</a>
|
||||
</section>
|
||||
{% endblock %} {% block extra_js %}
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", (event) => {
|
||||
// Syntax highlighting
|
||||
document.querySelectorAll("#markdown-content pre code").forEach((block) => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
|
||||
// Copy to clipboard functionality
|
||||
const copyButton = document.getElementById("copyButton");
|
||||
const markdownContent = document.getElementById("markdown-content");
|
||||
|
||||
copyButton.addEventListener("click", () => {
|
||||
const textToCopy = markdownContent.innerText;
|
||||
navigator.clipboard
|
||||
.writeText(textToCopy)
|
||||
.then(() => {
|
||||
copyButton.textContent = "Copied!";
|
||||
copyButton.classList.remove("bg-gray-200", "text-gray-700");
|
||||
copyButton.classList.add("bg-green-500", "text-white");
|
||||
setTimeout(() => {
|
||||
copyButton.innerHTML =
|
||||
'<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>Copy to Clipboard';
|
||||
copyButton.classList.remove("bg-green-500", "text-white");
|
||||
copyButton.classList.add("bg-gray-200", "text-gray-700");
|
||||
}, 2000);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to copy text: ", err);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -5,6 +5,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PenParse - Intelligent OCR for Handwritten Notes</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
</head>
|
||||
<body class="bg-gray-100 font-sans">
|
||||
<header class="bg-white shadow-sm">{% include "partial/nav.html" %}</header>
|
||||
|
|
|
@ -13,6 +13,11 @@ urlpatterns = [
|
|||
views.document_thumbnail,
|
||||
name="document_thumbnail",
|
||||
),
|
||||
path(
|
||||
"documents/<str:pk>/image",
|
||||
views.document_image,
|
||||
name="document_image",
|
||||
),
|
||||
path(
|
||||
"documents/<str:pk>/download", views.download_document, name="download_document"
|
||||
),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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",
|
||||
|
|
66
uv.lock
66
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"
|
||||
|
|
Loading…
Reference in New Issue