File size: 6,293 Bytes
c630093 3257702 c630093 |
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 |
import os
from fastapi import FastAPI, HTTPException, Request
from sse_starlette.sse import EventSourceResponse
from fastapi.staticfiles import StaticFiles
from openai import AsyncOpenAI
from fasthtml.common import FastHTML, Html, Head, Title, Body, Div, Button, Textarea, Script, Style, P, Favicon, ft_hx
import bleach
from styles import styles
from script import script
from fasthtml_hf import setup_hf_backup
secret_key = os.getenv('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():
"""Render homepage with FastGPT UI."""
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):
"""Stream responses for the given user input."""
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():
print(f"Client for session {session_id} 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)}"}
finally:
print(f"Streaming finished for session {session_id}")
return EventSourceResponse(event_generator())
@app.get("/reset")
def reset_conversation(session_id: str):
"""Reset the conversation for the specified session ID."""
if session_id in conversations:
del conversations[session_id]
return {"message": "Conversation reset."} |