html_generator.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. '''
  2. This is a library for formatting text outputs as nice HTML.
  3. '''
  4. import os
  5. import re
  6. import time
  7. from pathlib import Path
  8. import markdown
  9. from PIL import Image, ImageOps
  10. # This is to store the paths to the thumbnails of the profile pictures
  11. image_cache = {}
  12. with open(Path(__file__).resolve().parent / '../css/html_readable_style.css', 'r') as f:
  13. readable_css = f.read()
  14. with open(Path(__file__).resolve().parent / '../css/html_4chan_style.css', 'r') as css_f:
  15. _4chan_css = css_f.read()
  16. with open(Path(__file__).resolve().parent / '../css/html_cai_style.css', 'r') as f:
  17. cai_css = f.read()
  18. with open(Path(__file__).resolve().parent / '../css/html_instruct_style.css', 'r') as f:
  19. instruct_css = f.read()
  20. def fix_newlines(string):
  21. string = string.replace('\n', '\n\n')
  22. string = re.sub(r"\n{3,}", "\n\n", string)
  23. string = string.strip()
  24. return string
  25. # This could probably be generalized and improved
  26. def convert_to_markdown(string):
  27. string = string.replace('\\begin{code}', '```')
  28. string = string.replace('\\end{code}', '```')
  29. string = string.replace('\\begin{blockquote}', '> ')
  30. string = string.replace('\\end{blockquote}', '')
  31. string = re.sub(r"(.)```", r"\1\n```", string)
  32. string = fix_newlines(string)
  33. return markdown.markdown(string, extensions=['fenced_code'])
  34. def generate_basic_html(string):
  35. string = convert_to_markdown(string)
  36. string = f'<style>{readable_css}</style><div class="container">{string}</div>'
  37. return string
  38. def process_post(post, c):
  39. t = post.split('\n')
  40. number = t[0].split(' ')[1]
  41. if len(t) > 1:
  42. src = '\n'.join(t[1:])
  43. else:
  44. src = ''
  45. src = re.sub('>', '&gt;', src)
  46. src = re.sub('(&gt;&gt;[0-9]*)', '<span class="quote">\\1</span>', src)
  47. src = re.sub('\n', '<br>\n', src)
  48. src = f'<blockquote class="message">{src}\n'
  49. src = f'<span class="name">Anonymous </span> <span class="number">No.{number}</span>\n{src}'
  50. return src
  51. def generate_4chan_html(f):
  52. posts = []
  53. post = ''
  54. c = -2
  55. for line in f.splitlines():
  56. line += "\n"
  57. if line == '-----\n':
  58. continue
  59. elif line.startswith('--- '):
  60. c += 1
  61. if post != '':
  62. src = process_post(post, c)
  63. posts.append(src)
  64. post = line
  65. else:
  66. post += line
  67. if post != '':
  68. src = process_post(post, c)
  69. posts.append(src)
  70. for i in range(len(posts)):
  71. if i == 0:
  72. posts[i] = f'<div class="op">{posts[i]}</div>\n'
  73. else:
  74. posts[i] = f'<div class="reply">{posts[i]}</div>\n'
  75. output = ''
  76. output += f'<style>{_4chan_css}</style><div id="parent"><div id="container">'
  77. for post in posts:
  78. output += post
  79. output += '</div></div>'
  80. output = output.split('\n')
  81. for i in range(len(output)):
  82. output[i] = re.sub(r'^(&gt;(.*?)(<br>|</div>))', r'<span class="greentext">\1</span>', output[i])
  83. output[i] = re.sub(r'^<blockquote class="message">(&gt;(.*?)(<br>|</div>))', r'<blockquote class="message"><span class="greentext">\1</span>', output[i])
  84. output = '\n'.join(output)
  85. return output
  86. def make_thumbnail(image):
  87. image = image.resize((350, round(image.size[1] / image.size[0] * 350)), Image.Resampling.LANCZOS)
  88. if image.size[1] > 470:
  89. image = ImageOps.fit(image, (350, 470), Image.ANTIALIAS)
  90. return image
  91. def get_image_cache(path):
  92. cache_folder = Path("cache")
  93. if not cache_folder.exists():
  94. cache_folder.mkdir()
  95. mtime = os.stat(path).st_mtime
  96. if (path in image_cache and mtime != image_cache[path][0]) or (path not in image_cache):
  97. img = make_thumbnail(Image.open(path))
  98. output_file = Path(f'cache/{path.name}_cache.png')
  99. img.convert('RGB').save(output_file, format='PNG')
  100. image_cache[path] = [mtime, output_file.as_posix()]
  101. return image_cache[path][1]
  102. def generate_instruct_html(history):
  103. output = f'<style>{instruct_css}</style><div class="chat" id="chat">'
  104. for i, _row in enumerate(history[::-1]):
  105. row = [convert_to_markdown(entry) for entry in _row]
  106. output += f"""
  107. <div class="assistant-message">
  108. <div class="text">
  109. <div class="message-body">
  110. {row[1]}
  111. </div>
  112. </div>
  113. </div>
  114. """
  115. if len(row[0]) == 0: # don't display empty user messages
  116. continue
  117. output += f"""
  118. <div class="user-message">
  119. <div class="text">
  120. <div class="message-body">
  121. {row[0]}
  122. </div>
  123. </div>
  124. </div>
  125. """
  126. output += "</div>"
  127. return output
  128. def generate_cai_chat_html(history, name1, name2, reset_cache=False):
  129. output = f'<style>{cai_css}</style><div class="chat" id="chat">'
  130. # We use ?name2 and ?time.time() to force the browser to reset caches
  131. img_bot = f'<img src="file/cache/pfp_character.png?{name2}">' if Path("cache/pfp_character.png").exists() else ''
  132. img_me = f'<img src="file/cache/pfp_me.png?{time.time() if reset_cache else ""}">' if Path("cache/pfp_me.png").exists() else ''
  133. for i, _row in enumerate(history[::-1]):
  134. row = [convert_to_markdown(entry) for entry in _row]
  135. output += f"""
  136. <div class="message">
  137. <div class="circle-bot">
  138. {img_bot}
  139. </div>
  140. <div class="text">
  141. <div class="username">
  142. {name2}
  143. </div>
  144. <div class="message-body">
  145. {row[1]}
  146. </div>
  147. </div>
  148. </div>
  149. """
  150. if len(row[0]) == 0: # don't display empty user messages
  151. continue
  152. output += f"""
  153. <div class="message">
  154. <div class="circle-you">
  155. {img_me}
  156. </div>
  157. <div class="text">
  158. <div class="username">
  159. {name1}
  160. </div>
  161. <div class="message-body">
  162. {row[0]}
  163. </div>
  164. </div>
  165. </div>
  166. """
  167. output += "</div>"
  168. return output
  169. def generate_chat_html(history, name1, name2):
  170. return generate_cai_chat_html(history, name1, name2)
  171. def chat_html_wrapper(history, name1, name2, mode, reset_cache=False):
  172. if mode == "cai-chat":
  173. return generate_cai_chat_html(history, name1, name2, reset_cache)
  174. elif mode == "chat":
  175. return generate_chat_html(history, name1, name2)
  176. elif mode == "instruct":
  177. return generate_instruct_html(history)
  178. else:
  179. return ''