選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

app.py 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. from flask import ( Flask, render_template, request, abort, redirect,
  2. send_from_directory, url_for, session)
  3. from flask_session import Session
  4. import openai
  5. from dotenv import load_dotenv
  6. from markdown import markdown
  7. import requests
  8. from glob import glob
  9. import json
  10. import os
  11. import sys
  12. import time
  13. import shutil
  14. import re
  15. import secrets
  16. RE_VID = re.compile("""\[video ["']([^['"]*?)["']\]""")
  17. TEMPLATE_VID = """<video controls class="centered"><source src="{}" type="video/{}"></video>"""
  18. def preprocess_content(defs):
  19. md = defs.get("markdown", "")
  20. if md:
  21. content = markdown(md)
  22. content = RE_VID.sub(lambda m: TEMPLATE_VID.format(m.group(1), m.group(1).split('.')[-1]), content)
  23. else:
  24. content = ""
  25. img = defs.get("image", {})
  26. match img.get("placement", "none"):
  27. case "above":
  28. content = """<img src="{source}" alt="{alt}" class="centered w-{width}"/><br/>\n""".format(**img)+content
  29. case "left":
  30. content = """<img src="{source}" alt="{alt}" class="float-left w-{width}"/><br/>\n""".format(**img)+content
  31. case "right":
  32. content = """<img src="{source}" alt="{alt}" class="float-right w-{width}"/><br/>\n""".format(**img)+content
  33. case "below":
  34. content = content+"""<br/>\n<img src="{source}" alt="{alt}" class="centered w-{width}"/><br/>\n""".format(**img)
  35. if defs.get("use_soundtrack"):
  36. content = """<audio loop data-autoplay><source src="{}" type="audio/{}"></audio>\n""".format(
  37. defs["soundtrack"], defs["soundtrack"].split(".")[-1])+content
  38. return content
  39. def preprocess_payload(payload):
  40. for column in payload.get("columns", []):
  41. column["content"] = preprocess_content(column)
  42. for slide in column.get("slides", []):
  43. slide["content"] = preprocess_content(slide)
  44. def deref_schema(val):
  45. "Deref all `$ref` entries in a json-schema"
  46. if type(val)==type([]):
  47. return [deref_schema(item) for item in val]
  48. elif type(val)==type({}):
  49. val = dict(val)
  50. if "$ref" in val:
  51. ref = val["$ref"]
  52. if ref.startswith("#"):
  53. pass # stub. Currently we don't have internal references...
  54. elif ref.startswith("/"):
  55. ref = url_for("home", _external=True)+ref[1:]
  56. val.update(requests.get(ref).json())
  57. del val["$ref"]
  58. for key in val:
  59. val[key] = deref_schema(val[key])
  60. return val
  61. else:
  62. return val
  63. def file2json(path):
  64. "returns json string without line breaks and indent"
  65. return json.dumps(json.load(open(path)))
  66. def make_prompt():
  67. json_schema = json.dumps(schema())
  68. json_example = file2json("static/chat-example.json")
  69. return render_template("prompt.txt", schema=json_schema, example=json_example)
  70. load_dotenv()
  71. openai.organization = "org-GFWgNyt7NSKpCv6GhzXYZTpi"
  72. application = Flask(__name__)
  73. application.config["SECRET_KEY"] = secrets.token_hex()
  74. application.config["SESSION_TYPE"] = 'filesystem'
  75. Session(application)
  76. @application.route("/", methods=['GET', 'POST'])
  77. def home():
  78. is_initial = False
  79. if "messages" not in session:
  80. session["messages"] = [
  81. {"role": "system", "content": make_prompt()},
  82. {"role": "assistant", "content": file2json("static/initial-chat.json")}
  83. ]
  84. session["history"] = []
  85. is_initial = True
  86. prompt = ""
  87. if request.method=='POST' or is_initial:
  88. if is_initial:
  89. session["messages"] += [{"role": "system", "content": "User wishes to begin. Remember to find out how they feel before offering a ceremony or other action."}]
  90. else:
  91. prompt = request.form["prompt"].strip()
  92. if prompt:
  93. session["messages"] += [{"role": "user", "content": prompt}]
  94. else:
  95. session["messages"] += [{"role": "system", "content": "User has hit enter. Please continue."}]
  96. payload = None
  97. while not payload:
  98. reply = openai.ChatCompletion.create(model=os.environ["MODEL_NAME"], messages=session["messages"])
  99. message = dict(reply["choices"][0]["message"])
  100. session["messages"] += [message]
  101. try:
  102. payload = json.loads(message["content"])
  103. payload["prompt"] = prompt.replace('"', '\"')
  104. payload["content"] = preprocess_content(payload)
  105. session["history"] += [payload]
  106. except Exception as e:
  107. print(repr(e))
  108. session["messages"] += [{"role": "system", "content": "reply was ignored because it's not a valid json or doesn't comply with the schema. user is unaware. do not apologize to them"}]
  109. print("=====")
  110. print(reply["choices"][0]["message"]["content"])
  111. print("-----")
  112. print(payload)
  113. print("=====")
  114. else:
  115. payload = json.loads(session["messages"][-1]["content"])
  116. payload["content"] = preprocess_content(payload)
  117. payload["history"] = list(reversed(session["history"][:-1]))
  118. return render_template("chat.html", **payload)
  119. @application.get("/reset")
  120. def reset():
  121. for key in ["messages", "history"]:
  122. if key in session:
  123. del session[key]
  124. return redirect(url_for('home'))
  125. @application.get("/schema.json")
  126. def schema():
  127. "return deref_schema of `chat.schema.json` file's content"
  128. return deref_schema(json.load(open("static/chat.schema.json")))
  129. @application.get("/messages.json")
  130. def dump_messages():
  131. if "messages" not in session:
  132. session["messages"] = [
  133. {"role": "system", "content": make_prompt()},
  134. {"role": "assistant", "content": file2json("static/initial-chat.json")}
  135. ]
  136. return session["messages"]
  137. @application.get("/slides")
  138. def slides():
  139. "Legacy multy-slide format"
  140. payload = json.load(open("static/slides.json"))
  141. preprocess_payload(payload)
  142. return render_template("slides.html", generate_indices=False, **payload)
  143. @application.post("/update")
  144. def update():
  145. "Legacy multy-slide format"
  146. shutil.copy(
  147. "static/slides.json",
  148. time.strftime(
  149. "archive/slides-%Y-%m-%d-%H.%M.%S.json",
  150. time.localtime()))
  151. payload = request.get_json()
  152. print(type(payload))
  153. json.dump(payload, sys.stdout, indent=4)
  154. json.dump(payload, open("static/slides.json", "w"), indent=4)
  155. return {"status": "success"}
  156. @application.post("/update-chat")
  157. def update_chat():
  158. shutil.copy(
  159. "static/chat.json",
  160. time.strftime(
  161. "archive/chat-%Y-%m-%d-%H.%M.%S.json",
  162. time.localtime()))
  163. payload = request.get_json()
  164. print(type(payload))
  165. json.dump(payload, sys.stdout, indent=4)
  166. json.dump(payload, open("static/chat.json", "w"), indent=4)
  167. return {"status": "success"}
  168. @application.get("/enum/<topic>")
  169. def choices(topic):
  170. if topic in ["img", "bg", "bg-video", "audio"]:
  171. choices = glob("static/media/{}/*.*".format(topic))
  172. titles = [os.path.basename(c) for c in choices]
  173. try:
  174. descdir = json.load(open("static/media/{}.json".format(topic)))
  175. except FileNotFoundError:
  176. descdir = {}
  177. return {
  178. "type": "string",
  179. "enum": choices,
  180. "options": {
  181. "enum_titles": [descdir.get(t, t.rsplit(".", 1)[0]) for t in titles]
  182. }
  183. }
  184. abort(404)
  185. @application.route("/save", methods=['GET', 'POST'])
  186. def save():
  187. if request.method=="POST":
  188. filename=request.form["filename"].rsplit("/",1)[-1]
  189. if filename:
  190. moment = {
  191. key: session.get(key, [])
  192. for key in ["messages", "history"]
  193. }
  194. print(moment)
  195. json.dump(moment, open("archive/{}.json".format(filename),"w"), indent=4)
  196. return redirect(url_for("home"))
  197. else:
  198. return render_template("save.html",
  199. suggestion=time.strftime(
  200. "moment-%Y-%m-%d-%H.%M.%S"),
  201. files = [os.path.basename(path).rsplit(".",1)[0] for path in glob("archive/*.json")])
  202. @application.get("/chat-editor")
  203. def chat_editor():
  204. return render_template("chat-editor.html")
  205. @application.get("/editor")
  206. def editor():
  207. "Legacy multy-slide format"
  208. return render_template("editor.html")
  209. @application.route("/img", methods=['GET', 'POST'])
  210. def image():
  211. if request.method=='GET':
  212. src = "static/img/marble-question-mark.png"
  213. alt = "A question mark"
  214. prompt = ""
  215. else:
  216. prompt = request.form["prompt"]
  217. alt = prompt
  218. response = openai.Image.create(prompt=prompt, n=1, size="1024x1024")
  219. src = response['data'][0]['url']
  220. return render_template("image.html", src=src, alt=alt, prompt=prompt)
  221. @application.route("/favicon.ico")
  222. def favicon():
  223. return send_from_directory(os.path.join(application.root_path, "static"),
  224. "favicon.ico", mimetype="image/vnd.microsoft.icon")