''' This is a library for formatting GPT-4chan and chat outputs as nice HTML. ''' import os import re from pathlib import Path from PIL import Image # This is to store the paths to the thumbnails of the profile pictures image_cache = {} def generate_basic_html(s): css = """ .container { max-width: 600px; margin-left: auto; margin-right: auto; background-color: rgb(31, 41, 55); padding:3em; } .container p { font-size: 16px !important; color: white !important; margin-bottom: 22px; line-height: 1.4 !important; } """ s = '\n'.join([f'<p>{line}</p>' for line in s.split('\n')]) s = f'<style>{css}</style><div class="container">{s}</div>' return s def process_post(post, c): t = post.split('\n') number = t[0].split(' ')[1] if len(t) > 1: src = '\n'.join(t[1:]) else: src = '' src = re.sub('>', '>', src) src = re.sub('(>>[0-9]*)', '<span class="quote">\\1</span>', src) src = re.sub('\n', '<br>\n', src) src = f'<blockquote class="message">{src}\n' src = f'<span class="name">Anonymous </span> <span class="number">No.{number}</span>\n{src}' return src def generate_4chan_html(f): css = """ #parent #container { background-color: #eef2ff; padding: 17px; } #parent #container .reply { background-color: rgb(214, 218, 240); border-bottom-color: rgb(183, 197, 217); border-bottom-style: solid; border-bottom-width: 1px; border-image-outset: 0; border-image-repeat: stretch; border-image-slice: 100%; border-image-source: none; border-image-width: 1; border-left-color: rgb(0, 0, 0); border-left-style: none; border-left-width: 0px; border-right-color: rgb(183, 197, 217); border-right-style: solid; border-right-width: 1px; border-top-color: rgb(0, 0, 0); border-top-style: none; border-top-width: 0px; color: rgb(0, 0, 0); display: table; font-family: arial, helvetica, sans-serif; font-size: 13.3333px; margin-bottom: 4px; margin-left: 0px; margin-right: 0px; margin-top: 4px; overflow-x: hidden; overflow-y: hidden; padding-bottom: 4px; padding-left: 2px; padding-right: 2px; padding-top: 4px; } #parent #container .number { color: rgb(0, 0, 0); font-family: arial, helvetica, sans-serif; font-size: 13.3333px; width: 342.65px; margin-right: 7px; } #parent #container .op { color: rgb(0, 0, 0); font-family: arial, helvetica, sans-serif; font-size: 13.3333px; margin-bottom: 8px; margin-left: 0px; margin-right: 0px; margin-top: 4px; overflow-x: hidden; overflow-y: hidden; } #parent #container .op blockquote { margin-left: 0px !important; } #parent #container .name { color: rgb(17, 119, 67); font-family: arial, helvetica, sans-serif; font-size: 13.3333px; font-weight: 700; margin-left: 7px; } #parent #container .quote { color: rgb(221, 0, 0); font-family: arial, helvetica, sans-serif; font-size: 13.3333px; text-decoration-color: rgb(221, 0, 0); text-decoration-line: underline; text-decoration-style: solid; text-decoration-thickness: auto; } #parent #container .greentext { color: rgb(120, 153, 34); font-family: arial, helvetica, sans-serif; font-size: 13.3333px; } #parent #container blockquote { margin: 0px !important; margin-block-start: 1em; margin-block-end: 1em; margin-inline-start: 40px; margin-inline-end: 40px; margin-top: 13.33px !important; margin-bottom: 13.33px !important; margin-left: 40px !important; margin-right: 40px !important; } #parent #container .message { color: black; border: none; } """ posts = [] post = '' c = -2 for line in f.splitlines(): line += "\n" if line == '-----\n': continue elif line.startswith('--- '): c += 1 if post != '': src = process_post(post, c) posts.append(src) post = line else: post += line if post != '': src = process_post(post, c) posts.append(src) for i in range(len(posts)): if i == 0: posts[i] = f'<div class="op">{posts[i]}</div>\n' else: posts[i] = f'<div class="reply">{posts[i]}</div>\n' output = '' output += f'<style>{css}</style><div id="parent"><div id="container">' for post in posts: output += post output += '</div></div>' output = output.split('\n') for i in range(len(output)): output[i] = re.sub(r'^(>(.*?)(<br>|</div>))', r'<span class="greentext">\1</span>', output[i]) output[i] = re.sub(r'^<blockquote class="message">(>(.*?)(<br>|</div>))', r'<blockquote class="message"><span class="greentext">\1</span>', output[i]) output = '\n'.join(output) return output def get_image_cache(path): cache_folder = Path("cache") if not cache_folder.exists(): cache_folder.mkdir() mtime = os.stat(path).st_mtime if (path in image_cache and mtime != image_cache[path][0]) or (path not in image_cache): img = Image.open(path) img.thumbnail((200, 200)) output_file = Path(f'cache/{path.name}_cache.png') img.convert('RGB').save(output_file, format='PNG') image_cache[path] = [mtime, output_file.as_posix()] return image_cache[path][1] def generate_chat_html(history, name1, name2, character): css = """ .chat { margin-left: auto; margin-right: auto; max-width: 800px; height: 66.67vh; overflow-y: auto; padding-right: 20px; display: flex; flex-direction: column-reverse; } .message { display: grid; grid-template-columns: 60px 1fr; padding-bottom: 25px; font-size: 15px; font-family: Helvetica, Arial, sans-serif; line-height: 1.428571429; } .circle-you { width: 50px; height: 50px; background-color: rgb(238, 78, 59); border-radius: 50%; } .circle-bot { width: 50px; height: 50px; background-color: rgb(59, 78, 244); border-radius: 50%; } .circle-bot img, .circle-you img { border-radius: 50%; width: 100%; height: 100%; object-fit: cover; } .text { } .text p { margin-top: 5px; } .username { font-weight: bold; } .message-body { } .message-body img { max-width: 300px; max-height: 300px; border-radius: 20px; } .message-body p { margin-bottom: 0 !important; font-size: 15px !important; line-height: 1.428571429 !important; } .dark .message-body p em { color: rgb(138, 138, 138) !important; } .message-body p em { color: rgb(110, 110, 110) !important; } """ output = '' output += f'<style>{css}</style><div class="chat" id="chat">' img = '' for i in [ f"characters/{character}.png", f"characters/{character}.jpg", f"characters/{character}.jpeg", "img_bot.png", "img_bot.jpg", "img_bot.jpeg" ]: path = Path(i) if path.exists(): img = f'<img src="file/{get_image_cache(path)}">' break img_me = '' for i in ["img_me.png", "img_me.jpg", "img_me.jpeg"]: path = Path(i) if path.exists(): img_me = f'<img src="file/{get_image_cache(path)}">' break for i,_row in enumerate(history[::-1]): row = _row.copy() row[0] = re.sub(r"(\*\*)([^\*\n]*)(\*\*)", r"<b>\2</b>", row[0]) row[1] = re.sub(r"(\*\*)([^\*\n]*)(\*\*)", r"<b>\2</b>", row[1]) row[0] = re.sub(r"(\*)([^\*\n]*)(\*)", r"<em>\2</em>", row[0]) row[1] = re.sub(r"(\*)([^\*\n]*)(\*)", r"<em>\2</em>", row[1]) p = '\n'.join([f"<p>{x}</p>" for x in row[1].split('\n')]) output += f""" <div class="message"> <div class="circle-bot"> {img} </div> <div class="text"> <div class="username"> {name2} </div> <div class="message-body"> {p} </div> </div> </div> """ if not (i == len(history)-1 and len(row[0]) == 0): p = '\n'.join([f"<p>{x}</p>" for x in row[0].split('\n')]) output += f""" <div class="message"> <div class="circle-you"> {img_me} </div> <div class="text"> <div class="username"> {name1} </div> <div class="message-body"> {p} </div> </div> </div> """ output += "</div>" return output