Skip to content

Commit

Permalink
Merge pull request #19 from pamelafox/ndjson
Browse files Browse the repository at this point in the history
Port from SSE to NDJSON over ReadableStream
  • Loading branch information
pamelafox authored Jul 31, 2023
2 parents e11f92b + a023311 commit ae10d9d
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 23 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Build your own ChatGPT app

This repository includes a simple Python Flask app that streams responses from ChatGPT
to an HTML/JS frontend using [server-sent events](https://developer.mozilla.org/docs/Web/API/Server-sent_events/Using_server-sent_events).
to an HTML/JS frontend using [NDJSON](http://ndjson.org/) over a [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).

The repository is designed for use with [Docker containers](https://www.docker.com/), both for local development and deployment, and includes infrastructure files for deployment to [Azure Container Apps](https://learn.microsoft.com/azure/container-apps/overview). 🐳

Expand Down
13 changes: 3 additions & 10 deletions src/flaskapp/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ def index():
return render_template("index.html")


@bp.get("/chat")
@bp.post("/chat")
def chat_handler():
request_message = request.args.get("message")
request_message = request.json["message"]

@stream_with_context
def response_stream():
Expand All @@ -44,13 +44,6 @@ def response_stream():
)
for event in response:
current_app.logger.info(event)
if event["choices"][0]["delta"].get("role") == "assistant":
yield "event:start\ndata: stream\n\n"
if event["choices"][0]["delta"].get("content") is not None:
response_message = event["choices"][0]["delta"]["content"]
current_app.logger.info("Sending '%s'", response_message)
json_data = json.dumps({"text": response_message, "sender": "assistant"})
yield f"event:message\ndata: {json_data}\n\n"
yield "event: end\ndata: stream\n\n"
yield json.dumps(event).replace("\n", "\\n") + "\n"

return Response(response_stream(), mimetype="text/event-stream")
32 changes: 20 additions & 12 deletions src/flaskapp/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,18 @@
</form>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/showdown.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ndjson-readablestream.umd.js"></script>
<script>
let eventSource;
const form = document.getElementById("chat-form");
const messageInput = document.getElementById("message");
const targetContainer = document.getElementById("messages");
const userTemplate = document.querySelector('#message-template-user');
const assistantTemplate = document.querySelector('#message-template-assistant');
const converter = new showdown.Converter();

form.addEventListener("submit", function(e) {
form.addEventListener("submit", async function(e) {
e.preventDefault();
const message = messageInput.value;

Expand All @@ -84,19 +87,24 @@
let messageDiv = assistantTemplateClone.querySelector(".message-content");
targetContainer.appendChild(assistantTemplateClone);

eventSource = new EventSource(`/chat?message=${message}`);
eventSource.addEventListener("start", function(e) {
messageDiv.innerHTML = "";
});
eventSource.addEventListener("message", function(e) {
const message = JSON.parse(e.data);
messageDiv.innerHTML += message.text.replace("\n", "<br/>");
messageDiv.scrollIntoView();
});
eventSource.addEventListener('end', function(e) {
eventSource.close();
const response = await fetch("/chat", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({message: message})
});

messageDiv.innerHTML = "";
let answer = "";
for await (const event of readNDJSONStream(response.body)) {
if (event["choices"][0]["delta"]["content"]) {
answer += event["choices"][0]["delta"]["content"];
messageDiv.innerHTML = converter.makeHtml(answer);
messageDiv.scrollIntoView();
}
}

messageInput.value = "";
});
</script>
Expand Down

0 comments on commit ae10d9d

Please sign in to comment.