html_generator.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. '''
  2. This is a library for formatting GPT-4chan and chat outputs as nice HTML.
  3. '''
  4. import base64
  5. import copy
  6. import os
  7. import re
  8. from io import BytesIO
  9. from pathlib import Path
  10. from PIL import Image
  11. # This is to store chat profile pictures as base64-encoded thumbnails
  12. image_cache = {}
  13. def generate_basic_html(s):
  14. css = """
  15. .container {
  16. max-width: 600px;
  17. margin-left: auto;
  18. margin-right: auto;
  19. background-color: #D9D9D9;
  20. padding:3em;
  21. }
  22. .container p {
  23. font-size: 14px !important;
  24. color: black !important;
  25. font-family: Helvetica, Arial, sans-serif !important;
  26. line-height: 1.428571429 !important;
  27. margin-bottom: 22px;
  28. }
  29. """
  30. s = '\n'.join([f'<p>{line}</p>' for line in s.split('\n')])
  31. s = f'<style>{css}</style><div class="container">{s}</div>'
  32. return s
  33. def process_post(post, c):
  34. t = post.split('\n')
  35. number = t[0].split(' ')[1]
  36. if len(t) > 1:
  37. src = '\n'.join(t[1:])
  38. else:
  39. src = ''
  40. src = re.sub('>', '&gt;', src)
  41. src = re.sub('(&gt;&gt;[0-9]*)', '<span class="quote">\\1</span>', src)
  42. src = re.sub('\n', '<br>\n', src)
  43. src = f'<blockquote class="message">{src}\n'
  44. src = f'<span class="name">Anonymous </span> <span class="number">No.{number}</span>\n{src}'
  45. return src
  46. def generate_4chan_html(f):
  47. css = """
  48. #container {
  49. background-color: #eef2ff;
  50. padding: 17px;
  51. }
  52. .reply {
  53. background-color: rgb(214, 218, 240);
  54. border-bottom-color: rgb(183, 197, 217);
  55. border-bottom-style: solid;
  56. border-bottom-width: 1px;
  57. border-image-outset: 0;
  58. border-image-repeat: stretch;
  59. border-image-slice: 100%;
  60. border-image-source: none;
  61. border-image-width: 1;
  62. border-left-color: rgb(0, 0, 0);
  63. border-left-style: none;
  64. border-left-width: 0px;
  65. border-right-color: rgb(183, 197, 217);
  66. border-right-style: solid;
  67. border-right-width: 1px;
  68. border-top-color: rgb(0, 0, 0);
  69. border-top-style: none;
  70. border-top-width: 0px;
  71. color: rgb(0, 0, 0);
  72. display: table;
  73. font-family: arial, helvetica, sans-serif;
  74. font-size: 13.3333px;
  75. margin-bottom: 4px;
  76. margin-left: 0px;
  77. margin-right: 0px;
  78. margin-top: 4px;
  79. overflow-x: hidden;
  80. overflow-y: hidden;
  81. padding-bottom: 2px;
  82. padding-left: 2px;
  83. padding-right: 2px;
  84. padding-top: 2px;
  85. }
  86. .number {
  87. color: rgb(0, 0, 0);
  88. font-family: arial, helvetica, sans-serif;
  89. font-size: 13.3333px;
  90. width: 342.65px;
  91. }
  92. .op {
  93. color: rgb(0, 0, 0);
  94. font-family: arial, helvetica, sans-serif;
  95. font-size: 13.3333px;
  96. margin-bottom: 8px;
  97. margin-left: 0px;
  98. margin-right: 0px;
  99. margin-top: 4px;
  100. overflow-x: hidden;
  101. overflow-y: hidden;
  102. }
  103. .op blockquote {
  104. margin-left:7px;
  105. }
  106. .name {
  107. color: rgb(17, 119, 67);
  108. font-family: arial, helvetica, sans-serif;
  109. font-size: 13.3333px;
  110. font-weight: 700;
  111. margin-left: 7px;
  112. }
  113. .quote {
  114. color: rgb(221, 0, 0);
  115. font-family: arial, helvetica, sans-serif;
  116. font-size: 13.3333px;
  117. text-decoration-color: rgb(221, 0, 0);
  118. text-decoration-line: underline;
  119. text-decoration-style: solid;
  120. text-decoration-thickness: auto;
  121. }
  122. .greentext {
  123. color: rgb(120, 153, 34);
  124. font-family: arial, helvetica, sans-serif;
  125. font-size: 13.3333px;
  126. }
  127. blockquote {
  128. margin-block-start: 1em;
  129. margin-block-end: 1em;
  130. margin-inline-start: 40px;
  131. margin-inline-end: 40px;
  132. }
  133. """
  134. posts = []
  135. post = ''
  136. c = -2
  137. for line in f.splitlines():
  138. line += "\n"
  139. if line == '-----\n':
  140. continue
  141. elif line.startswith('--- '):
  142. c += 1
  143. if post != '':
  144. src = process_post(post, c)
  145. posts.append(src)
  146. post = line
  147. else:
  148. post += line
  149. if post != '':
  150. src = process_post(post, c)
  151. posts.append(src)
  152. for i in range(len(posts)):
  153. if i == 0:
  154. posts[i] = f'<div class="op">{posts[i]}</div>\n'
  155. else:
  156. posts[i] = f'<div class="reply">{posts[i]}</div>\n'
  157. output = ''
  158. output += f'<style>{css}</style><div id="container">'
  159. for post in posts:
  160. output += post
  161. output += '</div>'
  162. output = output.split('\n')
  163. for i in range(len(output)):
  164. output[i] = re.sub(r'^(&gt;(.*?)(<br>|</div>))', r'<span class="greentext">\1</span>', output[i])
  165. output[i] = re.sub(r'^<blockquote class="message">(&gt;(.*?)(<br>|</div>))', r'<blockquote class="message"><span class="greentext">\1</span>', output[i])
  166. output = '\n'.join(output)
  167. return output
  168. def image_to_base64(path):
  169. mtime = os.stat(path).st_mtime
  170. if (path in image_cache and mtime != image_cache[path][0]) or (path not in image_cache):
  171. img = Image.open(path)
  172. img.thumbnail((100, 100))
  173. img_buffer = BytesIO()
  174. img.convert('RGB').save(img_buffer, format='PNG')
  175. image_cache[path] = [mtime, base64.b64encode(img_buffer.getvalue()).decode("utf-8")]
  176. return image_cache[path][1]
  177. def generate_chat_html(history, name1, name2, character):
  178. css = """
  179. .chat {
  180. margin-left: auto;
  181. margin-right: auto;
  182. max-width: 800px;
  183. height: 66.67vh;
  184. overflow-y: auto;
  185. padding-right: 20px;
  186. display: flex;
  187. flex-direction: column-reverse;
  188. }
  189. .message {
  190. display: grid;
  191. grid-template-columns: 60px 1fr;
  192. padding-bottom: 22px;
  193. font-size: 15px;
  194. font-family: Helvetica, Arial, sans-serif;
  195. line-height: 1.428571429;
  196. }
  197. .circle-you {
  198. width: 50px;
  199. height: 50px;
  200. background-color: rgb(244, 78, 59);
  201. border-radius: 50%;
  202. }
  203. .circle-bot {
  204. width: 50px;
  205. height: 50px;
  206. background-color: rgb(59, 78, 244);
  207. border-radius: 50%;
  208. }
  209. .circle-bot img, .circle-you img {
  210. border-radius: 50%;
  211. width: 100%;
  212. height: 100%;
  213. object-fit: cover;
  214. }
  215. .text {
  216. }
  217. .text p {
  218. margin-top: 5px;
  219. }
  220. .username {
  221. font-weight: bold;
  222. }
  223. .message-body {
  224. }
  225. .message-body img {
  226. max-width: 300px;
  227. max-height: 300px;
  228. border-radius: 20px;
  229. }
  230. .message-body p {
  231. margin-bottom: 0 !important;
  232. font-size: 15px !important;
  233. line-height: 1.428571429 !important;
  234. }
  235. """
  236. output = ''
  237. output += f'<style>{css}</style><div class="chat" id="chat">'
  238. img = ''
  239. for i in [
  240. f"characters/{character}.png",
  241. f"characters/{character}.jpg",
  242. f"characters/{character}.jpeg",
  243. "img_bot.png",
  244. "img_bot.jpg",
  245. "img_bot.jpeg"
  246. ]:
  247. path = Path(i)
  248. if path.exists():
  249. img = f'<img src="data:image/png;base64,{image_to_base64(path)}">'
  250. break
  251. img_me = ''
  252. for i in ["img_me.png", "img_me.jpg", "img_me.jpeg"]:
  253. path = Path(i)
  254. if path.exists():
  255. img_me = f'<img src="data:image/png;base64,{image_to_base64(path)}">'
  256. break
  257. for i,_row in enumerate(history[::-1]):
  258. row = _row.copy()
  259. row[0] = re.sub(r"(\*\*)([^\*\n]*)(\*\*)", r"<b>\2</b>", row[0])
  260. row[1] = re.sub(r"(\*\*)([^\*\n]*)(\*\*)", r"<b>\2</b>", row[1])
  261. row[0] = re.sub(r"(\*)([^\*\n]*)(\*)", r"<em>\2</em>", row[0])
  262. row[1] = re.sub(r"(\*)([^\*\n]*)(\*)", r"<em>\2</em>", row[1])
  263. p = '\n'.join([f"<p>{x}</p>" for x in row[1].split('\n')])
  264. output += f"""
  265. <div class="message">
  266. <div class="circle-bot">
  267. {img}
  268. </div>
  269. <div class="text">
  270. <div class="username">
  271. {name2}
  272. </div>
  273. <div class="message-body">
  274. {p}
  275. </div>
  276. </div>
  277. </div>
  278. """
  279. if not (i == len(history)-1 and len(row[0]) == 0):
  280. p = '\n'.join([f"<p>{x}</p>" for x in row[0].split('\n')])
  281. output += f"""
  282. <div class="message">
  283. <div class="circle-you">
  284. {img_me}
  285. </div>
  286. <div class="text">
  287. <div class="username">
  288. {name1}
  289. </div>
  290. <div class="message-body">
  291. {p}
  292. </div>
  293. </div>
  294. </div>
  295. """
  296. output += "</div>"
  297. return output