-
-
Notifications
You must be signed in to change notification settings - Fork 380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
llm web command - launches a web server #4
Comments
I'm tempted to add Datasette as a dependency for this. |
Maybe this becomes a two-way thing: it could be a Datasette plugin (which adds |
Prototype: import asyncio
from datasette import hookimpl, Response
import openai
CHAT = """
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Client</title>
</head>
<body>
<h1>WebSocket Client</h1>
<textarea id="message" rows="4" cols="50"></textarea><br>
<button onclick="sendMessage()">Send Message</button>
<div id="log" style="margin-top: 1em; white-space: pre-wrap;"></div>
<script>
const ws = new WebSocket(`ws://${location.host}/ws`);
ws.onmessage = function(event) {
console.log(event);
const log = document.getElementById('log');
log.textContent += event.data;
};
function sendMessage() {
const message = document.getElementById('message').value;
console.log({message, ws});
ws.send(message);
}
</script>
</body>
</html>
""".strip()
async def websocket_application(scope, receive, send):
from .cli import get_openai_api_key
openai.api_key = get_openai_api_key()
if scope["type"] != "websocket":
return Response.text("ws only", status=400)
while True:
event = await receive()
if event["type"] == "websocket.connect":
await send({"type": "websocket.accept"})
elif event["type"] == "websocket.receive":
message = event["text"]
async for chunk in await openai.ChatCompletion.acreate(
model="gpt-3.5-turbo",
messages=[{
"role": "user",
"content": message,
}],
stream=True,
):
content = chunk["choices"][0].get("delta", {}).get("content")
if content is not None:
await send({"type": "websocket.send", "text": content})
elif event["type"] == "websocket.disconnect":
break
def chat():
return Response.html(CHAT)
@hookimpl
def register_routes():
return [
(r"^/ws$", websocket_application),
(r"^/chat$", chat),
] I put that in entry_points={
"datasette": ["llm = llm.plugin"],
"console_scripts": ["llm=llm.cli:cli"],
}, And this in @cli.command()
def web():
from datasette.app import Datasette
import uvicorn
path = get_log_db_path()
if not os.path.exists(path):
sqlite_utils.Database(path).vacuum()
ds = Datasette(
[path],
metadata={
"databases": {
"log": {
"tables": {
"log": {
"sort_desc": "rowid",
}
}
}
}
},
)
uvicorn.run(ds.app(), host="0.0.0.0", port=8302) |
Mucked around with HTML and CSS a bit and got to this prototype: <div class="chat-container">
<div class="chat-bubble one">
<div>
<p>Hello, how are you?</p>
</div>
<img class="avatar" src="https://placekitten.com/40/40" alt="Person A Avatar">
</div>
<div class="chat-bubble two">
<div>
<p>I'm good, thanks! And you?</p>
</div>
<img class="avatar" src="https://placekitten.com/40/40" alt="Person B Avatar">
</div>
<div class="chat-bubble one">
<div>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Distinctio similique quos ratione omnis impedit, est mollitia amet</p><p>aspernatur inventore consectetur, autem dolorum at nemo! Voluptas modi eveniet culpa nobis id?
</div>
<img class="avatar" src="https://placekitten.com/40/40" alt="Person A Avatar">
</div>
<div class="chat-bubble two">
<div id="animatedText">
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Distinctio similique quos ratione omnis impedit, est mollitia amet</p><p>aspernatur inventore consectetur, autem dolorum at nemo! Voluptas modi eveniet culpa nobis id?
</div>
<img class="avatar" src="https://placekitten.com/40/40" alt="Person B Avatar">
</div>
</div>
<style>
.chat-container {
display: flex;
flex-direction: column;
align-items: flex-start;
font-family: Helvetica, sans-serif;
line-height: 1.35;
color: rgba(0, 0, 0, 0.8);
max-width: 600px;
}
.chat-bubble {
border-radius: 10px;
padding: 10px;
margin: 10px;
width: 85%;
border: 1px solid #ccc;
background-color: #e6e5ff;
display: flex;
align-items: start;
}
.chat-bubble.one {
border-color: #b9b7f2;
}
.chat-bubble.two {
/* darker darker green */
border-color: #98d798;
}
.chat-bubble.one img.avatar {
order: -1;
margin-right: 10px;
}
.chat-bubble.two {
background-color: #ccffcc;
align-self: flex-end;
justify-content: space-between;
}
.chat-bubble.two img.avatar {
order: 1;
margin-left: 10px;
}
.chat-bubble p {
margin-top: 0;
}
.chat-bubble p:last-of-type {
margin-bottom: 0;
}
</style>
<script>
var text = "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Distinctio similique quos ratione omnis impedit, est mollitia amet aspernatur inventore consectetur, autem dolorum at nemo! Voluptas modi eveniet culpa nobis id?";
var words = text.split(" ");
var container = document.getElementById("animatedText");
container.innerHTML = "";
function addWord(index) {
if (index < words.length) {
container.innerHTML += words[index] + " ";
setTimeout(function() {
addWord(index + 1);
}, 50);
}
}
addWord(0);
</script> |
I built a quick ASGI prototype demonstrating server-sent events here: https://gist.github.com/simonw/d3d4773666b863e628b1a60d5a20294d |
Pushed a prototype to the Currently needs a |
Hi, |
The code for this is pretty messy but it works. Refs #4
Why not just use gradio? In about 50 lines of code you could have tabs for a chat interface, displaying and interacting with your logs etc. I've been using it for a while and is terrific |
A command that launches a web server to allow you to both browse your logs and interact with APIs through your browser.
The text was updated successfully, but these errors were encountered: