[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/api *.py
|
||||||
recursive-include vreader/templates *
|
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 -t vreader:latest .
|
||||||
|
|
||||||
docker_build_release_dev:
|
docker_build_release_dev: build_tailwind
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--platform linux/amd64,linux/arm64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
-t gitea.va.reichard.io/evan/vreader:dev \
|
-t gitea.va.reichard.io/evan/vreader:dev \
|
||||||
--push .
|
--push .
|
||||||
|
|
||||||
docker_build_release_latest:
|
docker_build_release_latest: build_tailwind
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--platform linux/amd64,linux/arm64 \
|
--platform linux/amd64,linux/arm64 \
|
||||||
-t gitea.va.reichard.io/evan/vreader:latest \
|
-t gitea.va.reichard.io/evan/vreader:latest \
|
||||||
-t gitea.va.reichard.io/evan/vreader:`git describe --tags` \
|
-t gitea.va.reichard.io/evan/vreader:`git describe --tags` \
|
||||||
--push .
|
--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
|
## 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 {
|
pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
|
nodePackages.tailwindcss
|
||||||
python311
|
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 Blueprint
|
||||||
from flask import make_response, render_template
|
from flask import make_response, render_template, send_from_directory
|
||||||
from html_sanitizer import Sanitizer
|
from html_sanitizer import Sanitizer
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
from vreader.config import Config
|
from vreader.config import Config
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("common", __name__)
|
bp = Blueprint("common", __name__)
|
||||||
sanitizer = Sanitizer()
|
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"])
|
@bp.route("/", methods=["GET"])
|
||||||
def main_entry():
|
def main_entry():
|
||||||
# Get Files
|
# Get Files
|
||||||
@ -30,6 +42,7 @@ def main_entry():
|
|||||||
|
|
||||||
return make_response(render_template("index.html", articles=articles))
|
return make_response(render_template("index.html", articles=articles))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/articles/<id>", methods=["GET"])
|
@bp.route("/articles/<id>", methods=["GET"])
|
||||||
def article_item(id):
|
def article_item(id):
|
||||||
|
|
||||||
|
@ -4,17 +4,9 @@ from flask import Blueprint, request
|
|||||||
from vreader.config import Config
|
from vreader.config import Config
|
||||||
import vreader
|
import vreader
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint("v1", __name__, url_prefix="/api/v1")
|
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"])
|
@bp.route("/generate", methods=["POST"])
|
||||||
def generate():
|
def generate():
|
||||||
|
@ -4,18 +4,12 @@ from typing import Any, List
|
|||||||
import json
|
import json
|
||||||
import openai
|
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:
|
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}
|
{context}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INITIAL_PROMPT_TEMPLATE_OLD = """
|
|
||||||
The following is a video transcription. Write a comprehensive article in markdown utilizing the following content:
|
|
||||||
|
|
||||||
{context}
|
|
||||||
"""
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ChatCompletion:
|
class ChatCompletion:
|
||||||
id: str
|
id: str
|
||||||
@ -33,13 +27,12 @@ class OpenAIConnector:
|
|||||||
|
|
||||||
# self.model = "gpt-3.5-turbo-16k"
|
# self.model = "gpt-3.5-turbo-16k"
|
||||||
self.model = "gpt-3.5-turbo-1106"
|
self.model = "gpt-3.5-turbo-1106"
|
||||||
self.word_cap = 1000
|
|
||||||
openai.api_key = api_key
|
openai.api_key = api_key
|
||||||
|
|
||||||
|
|
||||||
def query(self, context: str) -> Any:
|
def query(self, context: str) -> Any:
|
||||||
# Create Initial Prompt
|
# Create Prompt
|
||||||
prompt = INITIAL_PROMPT_TEMPLATE.format(context = context)
|
prompt = PROMPT_TEMPLATE.format(context = context)
|
||||||
messages = [{"role": "user", "content": prompt}]
|
messages = [{"role": "user", "content": prompt}]
|
||||||
|
|
||||||
print("[OpenAIConnector] Running OAI Query")
|
print("[OpenAIConnector] Running OAI Query")
|
||||||
@ -59,6 +52,7 @@ class OpenAIConnector:
|
|||||||
# Return Response
|
# Return Response
|
||||||
return { "title": title, "content": content }
|
return { "title": title, "content": content }
|
||||||
|
|
||||||
|
|
||||||
def get_title(self, markdown: str):
|
def get_title(self, markdown: str):
|
||||||
lines = markdown.split('\n')
|
lines = markdown.split('\n')
|
||||||
for line in lines:
|
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">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||||
name="viewport"
|
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||||
content="width=device-width, initial-scale=0.9, user-scalable=no, viewport-fit=cover"
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
||||||
/>
|
<meta name="theme-color" content="#E5E7EB" media="(prefers-color-scheme: light)">
|
||||||
<title>VReader - {{ metadata.title }}</title>
|
<meta name="theme-color" content="#123636" media="(prefers-color-scheme: dark)">
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
|
<!-- Markdown Overrides -->
|
||||||
<style>
|
<style>
|
||||||
h1 {
|
h1 {
|
||||||
|
color: var(--color-primary-text);
|
||||||
font-size: 1.50em !important;
|
font-size: 1.50em !important;
|
||||||
font-weight: 500 !important;
|
font-weight: 500 !important;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
|
color: var(--color-primary-text);
|
||||||
font-size: 1.25em !important;
|
font-size: 1.25em !important;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
|
color: var(--color-secondary-text);
|
||||||
margin-top: 0.25em !important;
|
margin-top: 0.25em !important;
|
||||||
margin-bottom: 1.5em !important;
|
margin-bottom: 1.5em !important;
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
</head>
|
||||||
<body class="bg-slate-200">
|
<body class="text-ptext bg-secondary">
|
||||||
<header class="w-screen h-16 bg-slate-300 mb-5">
|
<header class="w-screen h-16 bg-secondary">
|
||||||
<div
|
<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>
|
<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>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div
|
<main class="relative overflow-hidden bg-primary">
|
||||||
id="content"
|
<div id="container" class="h-[100dvh] overflow-auto md:px-6 w-11/12 md:w-5/6 mx-auto mt-3">
|
||||||
class="w-11/12 md:w-5/6 mx-auto rounded px-10 py-5 bg-slate-300"
|
<div id="content" class="rounded bg-secondary p-6 mb-3">
|
||||||
>
|
<div class="flex justify-center w-full mb-6">
|
||||||
<div class="flex justify-center pb-5 w-full">
|
|
||||||
<a target="_blank" href="https://www.youtube.com/watch?v={{ metadata.video_id }}">
|
<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>
|
<img class="h-32 rounded" src="https://i.ytimg.com/vi_webp/{{ metadata.video_id }}/maxresdefault.webp"></img>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<hr class="border-gray-500 pb-5" />
|
<hr class="border-primary pb-5" />
|
||||||
{{ markdown_html|safe }}
|
{{ markdown_html|safe }}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -2,39 +2,50 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||||
name="viewport"
|
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||||
content="width=device-width, initial-scale=0.9, user-scalable=no, viewport-fit=cover"
|
<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>
|
<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>
|
</head>
|
||||||
<body class="bg-slate-200">
|
<body class="text-ptext bg-secondary">
|
||||||
<header class="w-screen h-16 bg-slate-300 mb-5">
|
<header class="w-screen h-16 bg-secondary">
|
||||||
<div
|
<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="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>
|
<span class="font-bold flex justify-center items-center">VReader</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main class="flex flex-col gap-4">
|
<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"
|
<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"
|
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 p-2 bg-gray-700 text-white">
|
<input type="text" placeholder="YouTube URL" class="w-full px-2 py-1 bg-tertiary text-stext rounded">
|
||||||
<button class="p-2 bg-gray-500 text-gray-800 hover:bg-gray-100" type="submit">Generate</button>
|
<button class="px-2 py-1 bg-tertiary text-stext hover:bg-primary rounded">Generate</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for article in articles %}
|
{% for article in articles %}
|
||||||
<a
|
<a
|
||||||
href="/articles/{{ article.video_id }}"
|
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"
|
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>
|
<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>
|
<span>{{ article.title }}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<div class="mb-0.5"></div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
const LOADING_SVG = `<svg
|
const LOADING_SVG = `<svg
|
||||||
@ -92,14 +103,6 @@
|
|||||||
return fetch(data.url, fetchObj).then((resp) => resp.json());
|
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(){
|
function generateAction(){
|
||||||
let inputEl = document.querySelector("input");
|
let inputEl = document.querySelector("input");
|
||||||
let inputVal = inputEl.value;
|
let inputVal = inputEl.value;
|
||||||
|
Loading…
Reference in New Issue
Block a user