[add] pwa, icons, banner, themes
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
146c2e25d0
commit
f85deba946
@ -1,2 +1,3 @@
|
||||
recursive-include vreader/api *.py
|
||||
recursive-include vreader/templates *
|
||||
recursive-include vreader/static *
|
||||
|
9
Makefile
9
Makefile
@ -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
|
||||
|
12
README.md
12
README.md
@ -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/VReader" 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
BIN
banner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
BIN
banner.xcf
Normal file
BIN
banner.xcf
Normal file
Binary file not shown.
@ -2,6 +2,7 @@
|
||||
|
||||
pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
nodePackages.tailwindcss
|
||||
python311
|
||||
];
|
||||
}
|
||||
|
16
tailwind.config.js
Normal file
16
tailwind.config.js
Normal 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: [],
|
||||
};
|
@ -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):
|
||||
|
||||
|
@ -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():
|
||||
|
@ -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:
|
||||
|
BIN
vreader/static/icons/icon512.png
Normal file
BIN
vreader/static/icons/icon512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
17
vreader/static/manifest.json
Normal file
17
vreader/static/manifest.json
Normal 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
68
vreader/static/style.css
Normal 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;
|
||||
}
|
||||
}
|
||||
*/
|
1
vreader/static/tailwind.css
Normal file
1
vreader/static/tailwind.css
Normal file
File diff suppressed because one or more lines are too long
@ -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>
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user