-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.py
158 lines (133 loc) · 6.15 KB
/
main.py
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."}