[add] pwa, icons, banner, themes
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Evan Reichard 2023-11-11 16:41:25 -05:00
parent 146c2e25d0
commit dc9f8cd9db
16 changed files with 211 additions and 80 deletions

View File

@ -1,2 +1,3 @@
recursive-include vreader/api *.py
recursive-include vreader/templates *
recursive-include vreader/static *

View File

@ -1,15 +1,18 @@
docker_build_local:
docker_build_local: build_tailwind
docker build -t vreader:latest .
docker_build_release_dev:
docker_build_release_dev: build_tailwind
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t gitea.va.reichard.io/evan/vreader:dev \
--push .
docker_build_release_latest:
docker_build_release_latest: build_tailwind
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t gitea.va.reichard.io/evan/vreader:latest \
-t gitea.va.reichard.io/evan/vreader:`git describe --tags` \
--push .
build_tailwind:
tailwind build -o ./vreader/static/tailwind.css --minify

View File

@ -1,6 +1,14 @@
# VReader
<p><img align="center" src="https://gitea.va.reichard.io/evan/VReader/raw/branch/master/banner.png"></p>
Turn YouTube videos into articles! I banged this one out in a couple of hours, so it's a bit scrappy. Will slowly improve it.
<p align="center">
<a href="https://drone.va.reichard.io/evan/AnthoLume" target="_blank">
<img src="https://drone.va.reichard.io/api/badges/evan/VReader/status.svg">
</a>
</p>
---
VReader allows you to take videos from YouTube and convert them into articles!
## Running Server

BIN
banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
banner.xcf Normal file

Binary file not shown.

View File

@ -2,6 +2,7 @@
pkgs.mkShell {
packages = with pkgs; [
nodePackages.tailwindcss
python311
];
}

16
tailwind.config.js Normal file
View File

@ -0,0 +1,16 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./vreader/templates/**/*.html", "./vreader/assets/**/*.{html,js}"],
theme: {
extend: {
colors: {
primary: "var(--color-primary)",
secondary: "var(--color-secondary)",
tertiary: "var(--color-tertiary)",
ptext: "var(--color-primary-text)",
stext: "var(--color-secondary-text)",
},
},
},
plugins: [],
};

View File

@ -1,13 +1,25 @@
from flask import Blueprint
from flask import make_response, render_template
from flask import make_response, render_template, send_from_directory
from html_sanitizer import Sanitizer
from markdown import markdown
from vreader.config import Config
import os
bp = Blueprint("common", __name__)
sanitizer = Sanitizer()
@bp.route("/static/<path:path>")
def static(path):
return send_from_directory("static", path)
@bp.route("/manifest.json")
def manifest():
return send_from_directory("static", "manifest.json")
@bp.route("/", methods=["GET"])
def main_entry():
# Get Files
@ -30,6 +42,7 @@ def main_entry():
return make_response(render_template("index.html", articles=articles))
@bp.route("/articles/<id>", methods=["GET"])
def article_item(id):

View File

@ -4,17 +4,9 @@ from flask import Blueprint, request
from vreader.config import Config
import vreader
bp = Blueprint("v1", __name__, url_prefix="/api/v1")
@bp.route("/articles", methods=["GET"])
def articles():
directory = str(Config.DATA_PATH)
all_files = os.listdir(directory)
markdown_files = [file for file in all_files if file.endswith(".md")]
articles = [parse_filename(file) for file in markdown_files]
return articles
@bp.route("/generate", methods=["POST"])
def generate():

View File

@ -4,18 +4,12 @@ from typing import Any, List
import json
import openai
INITIAL_PROMPT_TEMPLATE = """
PROMPT_TEMPLATE = """
The following is a video transcription. Write a fully comprehensive article in markdown appropriately utilizing subsections. Be sure to only use the following transcription to write the article:
{context}
"""
INITIAL_PROMPT_TEMPLATE_OLD = """
The following is a video transcription. Write a comprehensive article in markdown utilizing the following content:
{context}
"""
@dataclass
class ChatCompletion:
id: str
@ -33,13 +27,12 @@ class OpenAIConnector:
# self.model = "gpt-3.5-turbo-16k"
self.model = "gpt-3.5-turbo-1106"
self.word_cap = 1000
openai.api_key = api_key
def query(self, context: str) -> Any:
# Create Initial Prompt
prompt = INITIAL_PROMPT_TEMPLATE.format(context = context)
# Create Prompt
prompt = PROMPT_TEMPLATE.format(context = context)
messages = [{"role": "user", "content": prompt}]
print("[OpenAIConnector] Running OAI Query")
@ -59,6 +52,7 @@ class OpenAIConnector:
# Return Response
return { "title": title, "content": content }
def get_title(self, markdown: str):
lines = markdown.split('\n')
for line in lines:

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -0,0 +1,17 @@
{
"name": "VReader",
"short_name": "VReader",
"lang": "en-US",
"theme_color": "#123636",
"display": "standalone",
"scope": "/",
"start_url": "/",
"icons": [
{
"purpose": "any",
"sizes": "512x512",
"src": "/static/icons/icon512.png",
"type": "image/png"
}
]
}

68
vreader/static/style.css Normal file
View File

@ -0,0 +1,68 @@
/* ----------------------------- */
/* -------- PWA Styling -------- */
/* ----------------------------- */
html,
body {
overscroll-behavior-y: none;
margin: 0px;
}
html {
height: calc(100% + env(safe-area-inset-bottom));
padding: env(safe-area-inset-top) env(safe-area-inset-right) 0
env(safe-area-inset-left);
}
main {
height: calc(100dvh - 4rem - env(safe-area-inset-top));
}
#container {
padding-bottom: calc(5em + env(safe-area-inset-bottom) * 2);
}
/* No Scrollbar - IE, Edge, Firefox */
* {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* No Scrollbar - WebKit */
*::-webkit-scrollbar {
display: none;
}
/* ----------------------------- */
/* ----------- Theme ----------- */
/* ----------------------------- */
@media (prefers-color-scheme: light) {
html {
--color-primary: #f3f4f6;
--color-secondary: #e5e7eb;
--color-tertiary: #d1d5db;
--color-primary-text: #1f2937;
--color-secondary-text: #4b5563;
}
}
@media (prefers-color-scheme: dark) {
html {
--color-primary: #091b1b;
--color-secondary: #123636;
--color-tertiary: #1f5c5c;
--color-primary-text: #ffffff;
--color-secondary-text: #d6f1ed;
}
}
/*
@media (prefers-color-scheme: dark) {
html {
--color-primary: #091B1B;
--color-secondary: #123636;
--color-tertiary: #1F5C5C;
--color-primary-text: #FFFFFF;
--color-secondary-text: #D6F1ED;
}
}
*/

File diff suppressed because one or more lines are too long

View File

@ -2,45 +2,59 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=0.9, user-scalable=no, viewport-fit=cover"
/>
<title>VReader - {{ metadata.title }}</title>
<script src="https://cdn.tailwindcss.com"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
<meta name="theme-color" content="#E5E7EB" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#123636" media="(prefers-color-scheme: dark)">
<!-- Markdown Overrides -->
<style>
h1 {
color: var(--color-primary-text);
font-size: 1.50em !important;
font-weight: 500 !important;
}
h2 {
color: var(--color-primary-text);
font-size: 1.25em !important;
}
p {
color: var(--color-secondary-text);
margin-top: 0.25em !important;
margin-bottom: 1.5em !important;
}
</style>
<title>VReader - {{ metadata.title }}</title>
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="/static/tailwind.css">
</head>
<body class="bg-slate-200">
<header class="w-screen h-16 bg-slate-300 mb-5">
<body class="text-ptext bg-secondary">
<header class="w-screen h-16 bg-secondary">
<div
class="flex px-2 h-16 w-11/12 md:w-5/6 mx-auto rounded bg-slate-300"
class="flex px-2 h-16 w-11/12 md:w-5/6 mx-auto"
>
<a class="font-bold flex justify-center items-center" href="/">All Articles</a>
</div>
</header>
<div
id="content"
class="w-11/12 md:w-5/6 mx-auto rounded px-10 py-5 bg-slate-300"
>
<div class="flex justify-center pb-5 w-full">
<a target="_blank" href="https://www.youtube.com/watch?v={{ metadata.video_id }}">
<img class="h-32 rounded" src="https://i.ytimg.com/vi_webp/{{ metadata.video_id }}/maxresdefault.webp"></img>
<a class="flex gap-2 justify-center items-center" href="/">
<img class="h-10 rounded items-center" src="/static/icons/icon512.png"></img>
<span class="font-bold flex justify-center items-center">VReader</span>
</a>
</div>
<hr class="border-gray-500 pb-5" />
{{ markdown_html|safe }}
</div>
</header>
<main class="relative overflow-hidden bg-primary">
<div id="container" class="h-[100dvh] overflow-auto md:px-6 w-11/12 md:w-5/6 mx-auto mt-3">
<div id="content" class="rounded bg-secondary p-6 mb-3">
<div class="flex justify-center w-full mb-6">
<a target="_blank" href="https://www.youtube.com/watch?v={{ metadata.video_id }}">
<img class="h-32 rounded" src="https://i.ytimg.com/vi_webp/{{ metadata.video_id }}/maxresdefault.webp"></img>
</a>
</div>
<hr class="border-primary pb-5" />
{{ markdown_html|safe }}
</div>
</div>
</main>
</body>
</html>

View File

@ -2,39 +2,50 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=0.9, user-scalable=no, viewport-fit=cover"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
<meta name="theme-color" content="#E5E7EB" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#123636" media="(prefers-color-scheme: dark)">
<title>VReader - Home</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="/static/tailwind.css">
</head>
<body class="bg-slate-200">
<header class="w-screen h-16 bg-slate-300 mb-5">
<body class="text-ptext bg-secondary">
<header class="w-screen h-16 bg-secondary">
<div
class="flex px-2 h-16 w-11/12 md:w-5/6 mx-auto rounded bg-slate-300"
class="flex px-2 h-16 w-11/12 md:w-5/6 mx-auto"
>
<span class="font-bold flex justify-center items-center">VReader</span>
<a class="flex gap-2 justify-center items-center" href="/">
<img class="h-10 rounded items-center" src="/static/icons/icon512.png"></img>
<span class="font-bold flex justify-center items-center">VReader</span>
</a>
</div>
</header>
<main class="flex flex-col gap-4">
<div id="submit"
class="flex gap-4 items-center text-lg w-11/12 md:w-4/6 mx-auto rounded px-6 py-3 bg-slate-300"
>
<input type="text" placeholder="YouTube URL" class="w-full p-2 bg-gray-700 text-white">
<button class="p-2 bg-gray-500 text-gray-800 hover:bg-gray-100" type="submit">Generate</button>
</div>
<main class="relative overflow-hidden bg-primary">
<div id="container" class="h-[100dvh] flex flex-col gap-3 px-4 overflow-auto md:px-6 mt-3">
<div id="submit"
class="flex gap-4 items-center text-lg w-11/12 md:w-4/6 mx-auto rounded px-6 py-3 bg-secondary transition-all duration-200"
>
<input type="text" placeholder="YouTube URL" class="w-full px-2 py-1 bg-tertiary text-stext rounded">
<button class="px-2 py-1 bg-tertiary text-stext hover:bg-primary rounded">Generate</button>
</div>
{% for article in articles %}
<a
href="/articles/{{ article.video_id }}"
class="flex items-center text-lg w-11/12 md:w-4/6 mx-auto rounded px-6 py-3 bg-slate-300 hover:bg-slate-400 transition-all duration-200"
>
<img class="h-14 md:h-24 mr-6 rounded" src="https://i.ytimg.com/vi_webp/{{ article.video_id }}/maxresdefault.webp"></img>
<span>{{ article.title }}</span>
</a>
{% endfor %}
{% for article in articles %}
<a
href="/articles/{{ article.video_id }}"
class="flex items-center text-lg w-11/12 md:w-4/6 mx-auto rounded px-6 py-3 bg-secondary hover:bg-tertiary transition-all duration-200"
>
<img class="h-14 md:h-24 mr-6 rounded" src="https://i.ytimg.com/vi_webp/{{ article.video_id }}/maxresdefault.webp"></img>
<span>{{ article.title }}</span>
</a>
{% endfor %}
<div class="mb-0.5"></div>
</div>
</main>
<script>
const LOADING_SVG = `<svg
@ -92,14 +103,6 @@
return fetch(data.url, fetchObj).then((resp) => resp.json());
}
function getVideoArticle(videoID) {
return apiCall({
url: "/api/v1/generate",
method: "POST",
data: { video: videoID },
});
}
function generateAction(){
let inputEl = document.querySelector("input");
let inputVal = inputEl.value;