diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..3766cc8
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
Hello! Nice to meet you. What's up?
+
+
+
ah, just holding an example conversation with you
+
+
+
Got it! Fun stuff. What kind of projects are you working on these days?
+
+
+
LLM chatbot named chatbug 🪲
+
+
+
Cool name! Chatbug sounds like a friendly one. How's it going?
+
+
+
making a web ui with bottle and alpinejs
+
+
+
+
+
+
+
+
diff --git a/web/main.js b/web/main.js
new file mode 100644
index 0000000..85edb73
--- /dev/null
+++ b/web/main.js
@@ -0,0 +1,25 @@
+// import {createApp, ref, reactive} from 'vue';
+
+
+
+
+// const app = createApp({
+// data() {
+
+// let msg = ref("hello world")
+
+// try {
+// msg.value = "" + pywebview.api
+// } catch (e) {
+// msg.value = "did not invoke " + e
+// }
+
+// window.msg = msg
+// return {
+// message: msg
+// };
+// }
+// });
+// app.mount('#app');
+
+
diff --git a/web/stylesheet.css b/web/stylesheet.css
new file mode 100644
index 0000000..0553e82
--- /dev/null
+++ b/web/stylesheet.css
@@ -0,0 +1,117 @@
+body {
+ background-color: black;
+ color: white;
+ font-family: Arial, Helvetica, sans-serif;
+ margin: 0px;
+ height: 100%;
+}
+
+
+.sidebar {
+ width: 250px;
+ background-color: #2a262a;
+ float: left;
+ height: 100%;
+ position: absolute;
+}
+
+.sidebar h1 {
+ margin: 20px;
+}
+
+
+.sidebar .title {
+ font-size: 8pt;
+ margin: 20px;
+ margin-top: 30px;
+ margin-bottom: 10px;
+}
+.sidebar .button {
+ margin-left: 10px;
+ margin-right: 10px;
+ padding: 10px;
+ border-radius: 10px;
+}
+.sidebar .button:hover {
+ background-color: #423a42;
+}
+
+.mainarea {
+ margin-left: 260px;
+ height: 100%;
+ position: absolute;
+ right: 0;
+ left: 0;
+}
+
+.message {
+ display: flex;
+ margin-left: 40px;
+ margin-right: 10px;
+
+}
+
+.bubble {
+ padding: 10px;
+ border-radius: 10px;
+ background-color: #416146;
+ margin-left: auto;
+ float: right;
+ position: relative;
+}
+
+.response {
+ display: flex;
+ margin: 30px;
+ position: relative;
+}
+
+.response::before {
+ content: '🪲';
+ position: absolute;
+ top: -4px;
+ left: -30px;
+}
+
+
+.input {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px;
+ background-color: #2a262a;
+ border-radius: 10px;
+ width: 70%;
+ margin: auto;
+ position: absolute;
+ bottom: 40px;
+}
+
+.tool.list {
+ display: none;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
+}
+
+.tool.button {
+ cursor: pointer;
+ padding: 5px 10px;
+ margin: 5px;
+}
+
+.input input {
+ flex-grow: 1;
+ padding: 10px;
+ border: 0px solid #ccc;
+ background: none;
+ color: white;
+}
+.input input:focus {
+ outline: 0px solid black; /* Custom focus outline */
+
+}
\ No newline at end of file
diff --git a/web/watchdog.js b/web/watchdog.js
new file mode 100644
index 0000000..d67ece9
--- /dev/null
+++ b/web/watchdog.js
@@ -0,0 +1,67 @@
+
+
+wdt = {
+ last_wdt_time: 0,
+ watchdog_counter: 0
+}
+
+pollFileChange = () => {
+ setTimeout(() => {
+ wdt.watchdog_counter++
+ console.log(wdt.watchdog_counter)
+ if (wdt.watchdog_counter > 20) {
+ return
+ }
+ ajax({
+ type: "GET",
+ url: "/watchdog",
+ success: (data) => {
+ var time = Number(data)
+ if (wdt.last_wdt_time == 0) {
+ wdt.last_wdt_time = time
+ pollFileChange()
+ } else if (time > wdt.last_wdt_time) {
+ location.reload();
+ } else {
+ pollFileChange()
+ }
+ },
+ })
+ }, 10000)
+}
+
+function ajax(setting) {
+ if (typeof(shutdown) !== 'undefined') return
+ var request = new XMLHttpRequest();
+ request.open(setting.type, setting.url, true);
+ request.setRequestHeader('Content-Type', setting.dataType)
+ request.onload = function(data) {
+ if (typeof(shutdown) !== 'undefined') return
+ if (this.status >= 200 && this.status < 400) {
+ if (setting.success) {
+ setting.success(this.response)
+ }
+ } else {
+ if (setting.error) {
+ setting.error(this.response)
+ }
+ }
+ }
+ request.onerror = function(data) {
+ if (typeof(shutdown) !== 'undefined') return
+ if (setting.error) {
+ setting.error(data)
+ }
+ }
+ if (setting.data) {
+ request.send(setting.data)
+ } else {
+ request.send()
+ }
+}
+
+
+
+pollFileChange()
+
+