File size: 6,263 Bytes
189cc01 cd73c4f 3df74c5 189cc01 cd73c4f 189cc01 cd73c4f 189cc01 a6d02e3 cd73c4f 189cc01 cd73c4f 189cc01 cd73c4f 189cc01 cd73c4f 189cc01 cd73c4f 189cc01 a6d02e3 189cc01 a6d02e3 189cc01 a6d02e3 54ffdc7 189cc01 a6d02e3 189cc01 a6d02e3 189cc01 a6d02e3 189cc01 a6d02e3 189cc01 3df74c5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
import os
import secrets
from fastapi import HTTPException, Request
from fastapi.staticfiles import StaticFiles
from sse_starlette.sse import EventSourceResponse
from openai import AsyncOpenAI
from fasthtml.common import (
FastHTML, Html, Head, Title, Body, Div, Button, Textarea,
Script, Style, P, Favicon, ft_hx
)
from styles import styles
from script import script
from fasthtml_hf import setup_hf_backup
import bleach
def get_or_create_secret_key(file_path=".sesskey"):
if os.path.exists(file_path):
with open(file_path, "r") as f:
return f.read().strip()
key = secrets.token_urlsafe(32)
with open(file_path, "w") as f:
f.write(key)
return key
secret_key = get_or_create_secret_key()
app = FastHTML(secret_key=secret_key)
app.mount("/static", StaticFiles(directory="static"), name="static")
setup_hf_backup(app)
client = AsyncOpenAI()
conversations = {}
ALLOWED_TAGS = list(bleach.sanitizer.ALLOWED_TAGS) + [
"h1", "h2", "h3", "p", "strong", "em", "ul", "ol", "li", "code", "pre", "blockquote"
]
ALLOWED_ATTRIBUTES = bleach.sanitizer.ALLOWED_ATTRIBUTES
static_dir = os.path.join(os.path.dirname(__file__), "static")
light_icon = os.path.join(static_dir, "favicon-light.ico")
dark_icon = os.path.join(static_dir, "favicon-dark.ico")
def Svg(*c, viewBox=None, **kwargs):
return ft_hx("svg", *c, viewBox=viewBox, **kwargs)
def Path(*c, d=None, fill=None, **kwargs):
return ft_hx("path", *c, d=d, fill=fill, **kwargs)
@app.get("/")
def home():
home_text = """
## FastGPT - A ChatGPT Implementation Using FastHTML
"""
page = Html(
Head(
Title("FastGPT"),
Favicon(light_icon="/static/favicon-light.ico", dark_icon="/static/favicon-dark.ico"),
Style(styles),
Script(src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"),
Script(src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.2.9/purify.min.js")
),
Body(
Div(
Div("FastGPT", _class="logo-text"),
Div(
Button(
Svg(
Path(
d="M441 58.9L453.1 71c9.4 9.4 9.4 24.6 0 33.9L424 134.1 377.9 88 407 58.9c9.4-9.4 24.6-9.4 33.9 0zM209.8 256.2L344 121.9 390.1 168 255.8 302.2c-2.9 2.9-6.5 5-10.4 6.1l-58.5 16.7 16.7-58.5c1.1-3.9 3.2-7.5 6.1-10.4zM373.1 25L175.8 222.2c-8.7 8.7-15 19.4-18.3 31.1l-28.6 100c-2.4 8.4-.1 17.4 6.1 23.6s15.2 8.5 23.6 6.1l100-28.6c11.8-3.4 22.5-9.7 31.1-18.3L487 138.9c28.1-28.1 28.1-73.7 0-101.8L474.9 25C446.8-3.1 401.2-3.1 373.1 25zM88 64C39.4 64 0 103.4 0 152L0 424c0 48.6 39.4 88 88 88l272 0c48.6 0 88-39.4 88-88l0-112c0-13.3-10.7-24-24-24s-24 10.7-24 24l0 112c0 22.1-17.9 40-40 40L88 464c-22.1 0-40-17.9-40-40l0-272c0-22.1 17.9-40 40-40l112 0c13.3 0 24-10.7 24-24s-10.7-24-24-24L88 64z",
fill="#b4b4b4"
),
viewBox="0 0 512 512",
_class="refresh-icon"
),
onclick="location.reload()",
_class="refresh-button"
),
_class="refresh-container"
),
_class="header"
),
Div(
Div(
Div(id="home-text-container", _class="markdown-container", **{"data-home-text": home_text}),
_class="title-wrapper"
),
P(id="output"),
Div(
Textarea(
id="message",
rows=1,
cols=50,
placeholder="Message FastGPT",
oninput="autoResizeTextarea()",
onkeypress="checkEnter(event)"
),
Button(
Svg(
Path(
d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2 160 448c0 17.7 14.3 32 32 32s32-14.3 32-32l0-306.7L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"
),
viewBox="0 0 384 512",
_class="send-icon"
),
onclick="sendMessage()",
_class="send-button"
),
_class="container"
),
_class="wrapper"
),
Script(script)
)
)
return page
@app.get("/stream")
async def stream_response(request: Request, message: str, session_id: str = None):
if not message:
raise HTTPException(status_code=400, detail="Message parameter is required")
if not session_id:
raise HTTPException(status_code=400, detail="Session ID is required")
if session_id not in conversations:
conversations[session_id] = [
{"role": "system", "content": "You are a helpful assistant. Use Markdown for formatting."}
]
conversations[session_id].append({"role": "user", "content": message})
async def event_generator():
try:
response = await client.chat.completions.create(
model="gpt-4o-mini",
messages=conversations[session_id],
stream=True
)
assistant_response = ""
async for chunk in response:
if await request.is_disconnected():
break
content = chunk.choices[0].delta.content
if content:
assistant_response += content
yield {"data": content}
conversations[session_id].append({"role": "assistant", "content": assistant_response})
except Exception as e:
yield {"data": f"Error: {str(e)}"}
return EventSourceResponse(event_generator())
@app.get("/reset")
def reset_conversation(session_id: str):
if session_id in conversations:
del conversations[session_id]
return {"message": "Conversation reset."} |