commit 75c5c2db63de6fc519897318c39d1ebeec0c055b Author: Charlotte Som Date: Tue Feb 25 17:39:29 2025 +0000 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f32964 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.venv +__pycache__ diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbb0ea6 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# llm-ui + +```shell +$ uv venv +$ uv pip install -r requirements.txt +$ uv run uvicorn server:app +``` diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..620b0c7 Binary files /dev/null and b/requirements.txt differ diff --git a/server/__init__.py b/server/__init__.py new file mode 100644 index 0000000..935c59b --- /dev/null +++ b/server/__init__.py @@ -0,0 +1,11 @@ +from server.http import Starlette, Route, Request, Response, JSONResponse, WebSocketRoute +from server.inference import list_conversations, connect_to_conversation + +async def status(request: Request) -> Response: + return JSONResponse({"status": "ok"}) + +app = Starlette(debug=True, routes=[ + Route("/api/", status), + Route("/api/conversation", list_conversations, methods=["GET"]), + WebSocketRoute("/api/conversation/:conversation/connect", connect_to_conversation) +]) diff --git a/server/http.py b/server/http.py new file mode 100644 index 0000000..e0572b0 --- /dev/null +++ b/server/http.py @@ -0,0 +1,5 @@ +from starlette.applications import Starlette +from starlette.routing import Route, WebSocketRoute +from starlette.responses import * +from starlette.requests import * +from starlette.websockets import WebSocket diff --git a/server/inference.py b/server/inference.py new file mode 100644 index 0000000..0c1fd0d --- /dev/null +++ b/server/inference.py @@ -0,0 +1,33 @@ +import llm, llm.cli, sqlite_utils +from .http import Request, JSONResponse, WebSocket, RedirectResponse +import json + +db = sqlite_utils.Database(llm.cli.logs_db_path()) + +async def list_conversations(request: Request): + conversations = [] + for row in db["conversations"].rows: + conversations.append({ "id": row["id"], "name": row["name"] }) + return JSONResponse(conversations) + +girlypop_prompt = llm.cli.load_template("girlypop").system + +async def connect_to_conversation(ws: WebSocket): + conversation_id = ws.path_params["conversation"] + if conversation_id == "new": + conversation = llm.AsyncConversation(llm.get_async_model()) + else: + try: + conversation: llm.AsyncConversation = llm.cli.load_conversation(conversation_id, async_=True) + except: + await ws.send_denial_response(JSONResponse({ + "error": "unable to load conversation {}".format(conversation_id) + })) + return + + await ws.accept() + async for message in ws.iter_text(): + response = conversation.prompt(message, system=girlypop_prompt) + async for chunk in response: + ws.send_text(json.dumps({"c": chunk})) + ws.send_text(json.dumps({"d": True})) # done