diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f8cb3d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +BIN = ./.venv/bin/ +PYTHON = ./.venv/bin/python +PIP = ./.venv/bin/pip + +APP_NAME=llmbox + +all: install build test + +ensure_repo: + -@git init + +ensure_env: ensure_repo + -@python3 -m venv .venv + +install: ensure_env + $(PIP) install -e . + +format: ensure_env + $(PIP) install shed + . $(BIN)/activate && shed + +build: ensure_env + $(MAKE) format + $(PIP) install build + $(PYTHON) -m build + +serve: ensure_env + $(BIN)serve --host=0.0.0.0 --port=8888 + +run: ensure_env + $(BIN)$(APP_NAME).run + +test: ensure_env + $(PYTHON) -m unittest $(APP_NAME).tests + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..07de284 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..a534621 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,25 @@ +[metadata] +name = llmbox +version = 1.0.0 +description = LLMBox frontend for LLM's +author = retoor +author_email = retoor@molodetz.nl +license = MIT +long_description = file: README.md +long_description_content_type = text/markdown + +[options] +packages = find: +package_dir = + = src +python_requires = >=3.7 +install_requires = + app @ git+https://molodetz.nl/retoor/app.git + yura @ git+https://molodetz.nl/retoor/yura.git + +[options.packages.find] +where = src + +[options.entry_points] +console_scripts = + llmbox.run = llmbox.__main__:run diff --git a/src/llmbox/__init__.py b/src/llmbox/__init__.py new file mode 100644 index 0000000..a3c1390 --- /dev/null +++ b/src/llmbox/__init__.py @@ -0,0 +1,7 @@ +import logging + +logging.basicConfig( + level=logging.INFO +) + +log = logging.getLogger(__name__) diff --git a/src/llmbox/__main__.py b/src/llmbox/__main__.py new file mode 100644 index 0000000..b6f5b76 --- /dev/null +++ b/src/llmbox/__main__.py @@ -0,0 +1,55 @@ +from llmbox.app import Application +import argparse + +def parse_args(): + + parser = argparse.ArgumentParser(description="LLMBox LLM frontend.") + + parser.add_argument( + "--host", + help="Host to server on. Default: 127.0.0.1.", + required=False, + default="127.0.0.1", + type=str + ) + parser.add_argument( + "--port", + help="Port to serve on. Default: 3020.", + default=3020, + required=False, + type=int + ) + parser.add_argument( + "--llm-name", + help="Name for the build of your LLM.", + default="llmbox", + required=False, + type=str + ) + parser.add_argument( + "--llm-extends", + help="Name of LLM to extend from, your basis. Must be loaded in Katya LLM server already.", + default="qwen:0.5b", + required=False, + type=str + ) + parser.add_argument( + "--llm-system", + help="Path to text file with LLM system messages. The context of your LLM will be described here.", + required=False, + type=str, + default="Be a bot named LLMBox written by retoor." + ) + + return parser.parse_args() + + +def run(): + args = parse_args() + app = Application(llm_name=args.llm_name, llm_extends=args.llm_extends, llm_system=args.llm_system) + app.run(host=args.host, port=args.port) + + +if __name__ == '__main__': + run() + diff --git a/src/llmbox/app.py b/src/llmbox/app.py new file mode 100644 index 0000000..9f662a9 --- /dev/null +++ b/src/llmbox/app.py @@ -0,0 +1,39 @@ +from app.app import Application as BaseApplication +from yura.client import AsyncClient +from llmbox import log +import pathlib +from aiohttp import web + +class Application(BaseApplication): + + + def __init__(self, llm_name, llm_extends, llm_system,server_url="https://flock.molodetz.nl", *args, **kwargs): + + self.server_url = server_url + self.client = AsyncClient(self.server_url) + + log.info("Server url: {}".format(server_url)) + log.info("LLM_name: {}".format(llm_name)) + log.info("LLM extends: {}".format(llm_extends)) + + self.llm_name = llm_name + self.llm_extends = llm_extends + self.llm_system = llm_system + + if pathlib.Path(self.llm_system).exists(): + self.llm_system = pathlib.Path(self.llm_system).read_text() + log.info("LLM system: {}".format(self.llm_system)) + else: + log.info("LLM system: {}".format(llm_system)) + + self.static_path = pathlib.Path(__file__).parent.joinpath("static") + + super().__init__(*args, **kwargs) + + self.router.add_get("/", self.handle_index) + + + async def handle_index(self, request): + index_content = self.static_path.joinpath("index.html").read_text() + return web.Response(text=index_content, content_type="text/html") + diff --git a/src/llmbox/static/app.js b/src/llmbox/static/app.js new file mode 100644 index 0000000..f355794 --- /dev/null +++ b/src/llmbox/static/app.js @@ -0,0 +1,68 @@ +class App { + url = null + socket = null + newMessage = null + constructor(){ + + this.url = window.location.href.replace(/^http/, 'ws') + 'ws/' + this.socket = new WebSocket(this.url); + const me = this + this.socket.onopen = async(event)=>{ + await this.onConnect(event) + } + this.socket.onmessage = (event)=>{ + me.onMessage(event) + } + + + } + createNewBotMessage(){ + let messageList = document.querySelector('.message-list') + let newMessage = document.createElement('div') + newMessage.classList.add('message') + newMessage.classList.add('bot') + newMessage.classList.add('botmessage') + messageList.appendChild(newMessage) + this.newMessage = newMessage + } + createNewUserMessage(msg){ + const messageList = document.querySelector('.message-list') + const newMessage = document.createElement('div') + newMessage.classList.add('message') + newMessage.classList.add('user') + newMessage.classList.add('usermessage') + newMessage.innerText = msg + messageList.appendChild(newMessage) + messageList.scrollTop = messageList.scrollHeight + } + + async chat(message){ + if(this.newMessage){ + return false; + } + this.createNewUserMessage(message) + this.createNewBotMessage() + await this.socket.send(JSON.stringify({"prompt":message})) + return true + } + async onConnect(event){ + await this.chat("What if the schrodinger cat said meow?") + } + onMessage(event){ + const messageList = document.querySelector('.message-list') + let obj = JSON.parse(event.data) + if(typeof(obj) == 'string'){ + this.newMessage.innerText += obj + + messageList.scrollTop = messageList.scrollHeight + + }else if (typeof(obj) == 'object' && obj['done']){ + this.newMessage = null; + messageList.scrollTop = messageList.scrollHeight + } + } + +} + +const app = new App() + diff --git a/src/llmbox/static/index.html b/src/llmbox/static/index.html new file mode 100644 index 0000000..c858f53 --- /dev/null +++ b/src/llmbox/static/index.html @@ -0,0 +1,143 @@ + + +
+ + +