From 87b6b3362db84884fe967702f8c843ec477c589a Mon Sep 17 00:00:00 2001 From: retoor <retoor@molodetz.nl> Date: Tue, 20 May 2025 04:20:10 +0200 Subject: [PATCH] New avatar. --- gitlog.jsonl | 497 ++++++++++++++++++ src/snek/templates/sandbox.html | 389 ++++++++++++++ src/snek/view/avatar.py | 23 + src/snek/view/avatar_animal.py | 871 ++++++++++++++++++++++++++++++++ 4 files changed, 1780 insertions(+) create mode 100644 src/snek/view/avatar_animal.py diff --git a/gitlog.jsonl b/gitlog.jsonl index 764c7df..010d184 100644 --- a/gitlog.jsonl +++ b/gitlog.jsonl @@ -990,3 +990,500 @@ {"repo": ".", "date": "2025-05-17", "line": "feat: Refactor index.html with improved styling and features overview", "commit": "c0b4ba715c329273e4f5684d1ec2e231e5a1c7e7", "diff": "commit c0b4ba715c329273e4f5684d1ec2e231e5a1c7e7\nAuthor: retoor <retoor@molodetz.nl>\nDate: Sat May 17 00:53:27 2025 +0200\n\n t:\n\ndiff --git a/src/snek/templates/index.html b/src/snek/templates/index.html\nindex a1e8894..e12fe2b 100644\n--- a/src/snek/templates/index.html\n+++ b/src/snek/templates/index.html\n@@ -1,31 +1,288 @@\n-<!DOCTYPE html>\n+\n+ <!DOCTYPE html>\n+ <html lang=\"en\">\n+ <head>\n+ <meta charset=\"UTF-8\">\n+ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n+\t\t\t\t\t\t\t<style>\n+\t\t\t\t\t\t\t\tbody {\n+\t\t\t\t\t\t\t\t}\n+\n+\t\t\t\t\t\t\t\t\n+ * { margin:0; padding:0; box-sizing:border-box; }\n+ body {\n+ font-family: 'Segoe UI',sans-serif;\n+ line-height:1.5;\n+ }\n+ a:hover { text-decoration: underline; }\n+\n+ .container { width: 90%; max-width: 960px; margin: auto; padding: 2rem 0; }\n+\n+ .hero {\n+ text-align: center;\n+ padding: 4rem 0;\n+ }\n+ .hero h1 {\n+ font-size: 3rem;\n+ -webkit-background-clip: text;\n+ color: transparent;\n+ }\n+ .hero p {\n+ font-size: 1.2rem;\n+ margin: 1rem 0 2rem;\n+ }\n+ .btn {\n+ display: inline-block;\n+ padding: .75rem 1.5rem;\n+ margin: .5rem;\n+ font-weight: bold;\n+ border-radius: 4px;\n+ transition: background .2s;\n+ }\n+\n+ .grid {\n+ display: grid;\n+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n+ gap: 1.5rem;\n+ margin-top: 2rem;\n+ }\n+ .card {\n+ border-radius: 6px;\n+ padding: 1.5rem;\n+ box-shadow: 0 2px 6px rgba(0,0,0,0.6);\n+ }\n+ .card h3 {\n+ margin-bottom: .75rem;\n+ }\n+ .card ul {\n+ list-style: disc inside;\n+ margin-top: .5rem;\n+ }\n+\n+ footer {\n+ text-align: center;\n+ font-size: .9rem;\n+ padding: 2rem 0;\n+ }\n+ footer code {\n+ padding: 2px 4px;\n+ border-radius: 3px;\n+ }\n+\n+ @media (max-width: 480px) {\n+ .hero h1 { font-size: 2.4rem; }\n+ .btn { width: 100%; box-sizing: border-box; text-align:center; }\n+ }\n+ \n+\n+\t\t\t\t\t\t\t</style>\n+ </head>\n+ <body>\n+ <!DOCTYPE html>\n <html lang=\"en\">\n <head>\n- <meta charset=\"UTF-8\">\n- <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n- <title>Snek chat by Molodetz</title>\n- <link rel=\"stylesheet\" href=\"generic-form.css\">\n- <link rel=\"stylesheet\" href=\"base.css\">\n-<style>\n- .registration-container {\n- max-width: 300px;\n- margin: 20px auto;\n- padding: 20px;\n- }\n-</style>\n- <script src=\"/fancy-button.js\"></script>\n+ <meta charset=\"UTF-8\" />\n+ <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n+ <title>Snek \u2013 The Ultimate Web Community</title>\n+ <style>\n+ * { margin:0; padding:0; box-sizing:border-box; }\n+ body {\n+ font-family: 'Segoe UI',sans-serif;\n+ line-height:1.5;\n+ }\n+ a:hover { text-decoration: underline; }\n+\n+ .container { width: 90%; max-width: 960px; margin: auto; padding: 2rem 0; }\n+\n+ .hero {\n+ text-align: center;\n+ padding: 4rem 0;\n+ }\n+ .hero h1 {\n+ font-size: 3rem;\n+ -webkit-background-clip: text;\n+ color: transparent;\n+ }\n+ .hero p {\n+ font-size: 1.2rem;\n+ margin: 1rem 0 2rem;\n+ }\n+ .btn {\n+ display: inline-block;\n+ padding: .75rem 1.5rem;\n+ margin: .5rem;\n+ font-weight: bold;\n+ border-radius: 4px;\n+ transition: background .2s;\n+ }\n+\n+ .grid {\n+ display: grid;\n+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n+ gap: 1.5rem;\n+ margin-top: 2rem;\n+ }\n+ .card {\n+ border-radius: 6px;\n+ padding: 1.5rem;\n+ box-shadow: 0 2px 6px rgba(0,0,0,0.6);\n+ }\n+ .card h3 {\n+ margin-bottom: .75rem;\n+ }\n+ .card ul {\n+ list-style: disc inside;\n+ margin-top: .5rem;\n+ }\n+\n+ footer {\n+ text-align: center;\n+ font-size: .9rem;\n+ padding: 2rem 0;\n+ }\n+ footer code {\n+ padding: 2px 4px;\n+ border-radius: 3px;\n+ }\n+\n+ @media (max-width: 480px) {\n+ .hero h1 { font-size: 2.4rem; }\n+ .btn { width: 100%; box-sizing: border-box; text-align:center; }\n+ }\n+ </style>\n </head>\n <body>\n- <div class=\"registration-container\">\n+\n+ <header class=\"container hero\">\n <h1>Snek</h1>\n- <p style=\"padding-bottom:20px\">Rocket Chat got bloated, too commercialized,\n- So Snek came through, lean and optimized.</p>\n- <div style=\"text-align: center;\">\n- <fancy-button url=\"/login.html\" text=\"Login\"></fancy-button>\n- <span style=\"padding:10px;\">OR</span>\n- <fancy-button url=\"/register.html\" text=\"Register\"></fancy-button>\n- </div>\n- </div>\n+ <p>The Ultimate Web Community for Devs, Testers & AI Enthusiasts</p>\n+ <a href=\"/login.html\" class=\"btn\">Login</a>\n+ <a href=\"/register.html\" class=\"btn\">Register</a>\n+ </header>\n+\n+ <main class=\"container\">\n+\n+ <section id=\"features\" class=\"grid\">\n+ <div class=\"card\">\n+ <h3>File Sharing</h3>\n+ <ul>\n+ <li>SFTP storage with your Snek credentials</li>\n+ <li>WebDAV support \u2013 same login</li>\n+ </ul>\n+ </div>\n+\n+ <div class=\"card\">\n+ <h3>Git Repositories</h3>\n+ <ul>\n+ <li>Configure repos for any official Git client</li>\n+ <li>Instant setup, push & pull</li>\n+ </ul>\n+ </div>\n+\n+ <div class=\"card\">\n+ <h3>AI Powerhouse</h3>\n+ <ul>\n+ <li>Chat with free & commercial AIs</li>\n+ <li>Generate AI-powered images</li>\n+ <li>Build your own AI bots in <5 minutes (copy/paste example)</li>\n+ </ul>\n+ </div>\n+\n+ <div class=\"card\">\n+ <h3>Dev & Terminal</h3>\n+ <ul>\n+ <li>Ubuntu web terminal in-browser</li>\n+ <li>Full profile & permissions management</li>\n+ </ul>\n+ </div>\n+\n+ <div class=\"card\">\n+ <h3>Chat & Media</h3>\n+ <ul>\n+ <li>Upload any file type in chat</li>\n+ <li>Rich media support (audio, video, images\u2026)</li>\n+ <li>Direct messaging with other users</li>\n+ </ul>\n+ </div>\n+\n+ <div class=\"card\">\n+ <h3>Privacy & Community</h3>\n+ <ul>\n+ <li>No logging\u2014never even your IP</li>\n+ <li>No email required to sign up</li>\n+ <li>Multi-national, open community</li>\n+ <li>Hacking encouraged!</li>\n+ </ul>\n+ </div>\n+\n+ <div class=\"card\">\n+ <h3>Customization & Deployment</h3>\n+ <ul>\n+ <li>Full layout & theme customization</li>\n+ <li>Install as a PWA on your phone</li>\n+ <li>Optionally self-host: <code>pip install snek</code>, zero config</li>\n+ </ul>\n+ </div>\n+ </section>\n+\n+ <section id=\"signup\" style=\"text-align:center; margin:4rem 0;\">\n+ <h2>Ready to join?</h2>\n+ <p>No email. No logs. Just sign up, pick a username, and dive in!</p>\n+ <a href=\"/register\" class=\"btn\">Sign Up Now</a>\n+ </section>\n+\n+ <section id=\"selfhost\" style=\"text-align:center; margin-bottom:4rem;\">\n+ <h2>Self-Host in Seconds</h2>\n+ <p>Just run:</p>\n+snek serve\n+ </pre>\n+ <p>No configuration required\u2014it's that simple.</p>\n+ </section>\n+\n+ </main>\n+\n+ <footer>\n+ <p>© 2025 Snek \u2013 Join our global community of developers, testers & AI enthusiasts.</p>\n+ </footer>\n+\n </body>\n </html>"} {"repo": ".", "date": "2025-05-17", "line": "feat: Refactor chat input component with improved auto-completion and live typing functionality", "commit": "48c3daf3983e3b6e04a0c5888febceb69db9d661", "diff": "commit 48c3daf3983e3b6e04a0c5888febceb69db9d661\nAuthor: retoor <retoor@molodetz.nl>\nDate: Sat May 17 00:54:15 2025 +0200\n\n Update.\n\ndiff --git a/src/snek/static/chat-input.js b/src/snek/static/chat-input.js\nindex c1d767d..2d0914e 100644\n--- a/src/snek/static/chat-input.js\n+++ b/src/snek/static/chat-input.js\n@@ -1,69 +1,234 @@\n-\n-\n-\n-\n-class ChatInputElement extends HTMLElement {\n- _chatWindow = null \n- constructor() {\n- super();\n- this.attachShadow({ mode: 'open' });\n- this.component = document.createElement('div');\n- this.shadowRoot.appendChild(this.component);\n- }\n- set chatWindow(value){\n- this._chatWindow = value \n-\n- }\n- get chatWindow(){\n- return this._chatWindow \n- }\n- get channelUid() {\n- return this.chatWindow.channel.uid\n- }\n- connectedCallback() {\n- const link = document.createElement('link');\n- link.rel = 'stylesheet';\n- link.href = '/base.css';\n- this.component.appendChild(link);\n-\n- this.container = document.createElement('div');\n- this.container.classList.add('chat-input');\n- this.container.innerHTML = `\n- <textarea placeholder=\"Type a message...\" rows=\"2\"></textarea>\n- <upload-button></upload-button>\n- `;\n- this.textBox = this.container.querySelector('textarea');\n- this.uploadButton = this.container.querySelector('upload-button');\n- this.uploadButton.chatInput = this \n- this.textBox.addEventListener('input', (e) => {\n- this.dispatchEvent(new CustomEvent('input', { detail: e.target.value, bubbles: true }));\n- const message = e.target.value;\n- const button = this.container.querySelector('button');\n- button.disabled = !message;\n- });\n-\n- this.textBox.addEventListener('change', (e) => {\n- e.preventDefault();\n- this.dispatchEvent(new CustomEvent('change', { detail: e.target.value, bubbles: true }));\n- console.error(e.target.value);\n- });\n-\n- this.textBox.addEventListener('keydown', (e) => {\n- if (e.key === 'Enter' && !e.shiftKey) {\n- e.preventDefault();\n- const message = e.target.value.trim();\n- if (!message) return;\n- this.dispatchEvent(new CustomEvent('submit', { detail: message, bubbles: true }));\n- e.target.value = '';\n- }\n- });\n-\n- this.component.appendChild(this.container);\n- }\n+\n+import { app } from '../app.js';\n+\n+class ChatInputComponent extends HTMLElement {\n+ autoCompletions = {\n+ 'example 1': () => {\n+\n+ },\n+ 'example 2': () => {\n+\n+ }\n+ }\n+\n+ constructor() {\n+ super();\n+ this.lastUpdateEvent = new Date();\n+ this.textarea = document.createElement(\"textarea\");\n+ this._value = \"\";\n+ this.value = this.getAttribute(\"value\") || \"\";\n+ this.previousValue = this.value;\n+ this.lastChange = new Date();\n+ this.changed = false;\n+ }\n+\n+ get value() {\n+ return this._value;\n+ }\n+\n+ set value(value) {\n+ this._value = value || \"\";\n+ this.textarea.value = this._value;\n+ }\n+\n+ resolveAutoComplete() {\n+ let count = 0;\n+ let value = null;\n+ Object.keys(this.autoCompletions).forEach((key) => {\n+ if (key.startsWith(this.value)) {\n+ count++;\n+ value = key;\n+ }\n+ });\n+ if (count == 1)\n+ return value;\n+ return null;\n+ }\n+\n+ isActive() {\n+ return document.activeElement === this.textarea;\n+ }\n+\n+ focus() {\n+ this.textarea.focus();\n+ }\n+\n+ connectedCallback() {\n+ this.liveType = this.getAttribute(\"live-type\") === \"true\";\n+ this.liveTypeInterval = parseInt(this.getAttribute(\"live-type-interval\")) || 3;\n+ this.channelUid = this.getAttribute(\"channel\");\n+ this.messageUid = null;\n+\n+ this.classList.add(\"chat-input\");\n+\n+ this.textarea.setAttribute(\"placeholder\", \"Type a message...\");\n+ this.textarea.setAttribute(\"rows\", \"2\");\n+\n+ this.appendChild(this.textarea);\n+\n+ this.uploadButton = document.createElement(\"upload-button\");\n+ this.uploadButton.setAttribute(\"channel\", this.channelUid);\n+ this.uploadButton.addEventListener(\"upload\", (e) => {\n+ this.dispatchEvent(new CustomEvent(\"upload\", e));\n+ });\n+ this.uploadButton.addEventListener(\"uploaded\", (e) => {\n+ this.dispatchEvent(new CustomEvent(\"uploaded\", e));\n+ });\n+\n+ this.appendChild(this.uploadButton);\n+\n+ this.textarea.addEventListener(\"keyup\", (e) => {\n+ if(e.key === 'Enter' && !e.shiftKey) {\n+ this.value = ''\n+ e.target.value = '';\n+ return \n+ }\n+ this.value = e.target.value;\n+ this.changed = true;\n+ this.update();\n+ });\n+\n+ this.textarea.addEventListener(\"keydown\", (e) => {\n+ this.value = e.target.value;\n+ if (e.key === \"Tab\") {\n+ e.preventDefault();\n+ let autoCompletion = this.resolveAutoComplete();\n+ if (autoCompletion) {\n+ e.target.value = autoCompletion;\n+ this.value = autoCompletion;\n+ return;\n+ }\n+ }\n+ if (e.key === 'Enter' && !e.shiftKey) {\n+ e.preventDefault();\n+\n+ const message = e.target.value;\n+ this.messageUid = null;\n+ this.value = '';\n+ this.previousValue = '';\n+\n+ if (!message) {\n+ return;\n+ }\n+\n+ let autoCompletion = this.autoCompletions[message];\n+ if (autoCompletion) {\n+ this.value = '';\n+ this.previousValue = '';\n+ e.target.value = '';\n+ autoCompletion();\n+ return;\n+ }\n+\n+ e.target.value = '';\n+ this.value = '';\n+ this.messageUid = null;\n+ this.sendMessage(this.channelUid, message).then((uid) => {\n+ this.messageUid = uid;\n+ });\n+ }\n+ });\n+\n+ this.changeInterval = setInterval(() => {\n+ if (!this.liveType) {\n+ return;\n+ }\n+ if (this.value !== this.previousValue) {\n+ if (this.trackSecondsBetweenEvents(this.lastChange, new Date()) >= this.liveTypeInterval) {\n+ this.value = '';\n+ this.previousValue = '';\n+ }\n+ this.lastChange = new Date();\n+ }\n+ this.update();\n+ }, 300);\n+\n+ this.addEventListener(\"upload\", (e) => {\n+ this.focus();\n+ });\n+ this.addEventListener(\"uploaded\", function (e) {\n+ let message = \"\";\n+ e.detail.files.forEach((file) => {\n+ message += `[${file.name}](/channel/attachment/${file.relative_url})`;\n+ });\n+ app.rpc.sendMessage(this.channelUid, message);\n+ });\n+ }\n+\n+ trackSecondsBetweenEvents(event1Time, event2Time) {\n+ const millisecondsDifference = event2Time.getTime() - event1Time.getTime();\n+ return millisecondsDifference / 1000;\n+ }\n+\n+ newMessage() {\n+ if (!this.messageUid) {\n+ this.messageUid = '?';\n+ }\n+\n+ this.sendMessage(this.channelUid, this.value).then((uid) => {\n+ this.messageUid = uid;\n+ });\n+ }\n+\n+ updateMessage() {\n+ if (this.value[0] == \"/\") {\n+ return false;\n+ }\n+ if (!this.messageUid) {\n+ this.newMessage();\n+ return false;\n+ }\n+ if (this.messageUid === '?') {\n+ return false;\n+ }\n+ if (typeof app !== \"undefined\" && app.rpc && typeof app.rpc.updateMessageText === \"function\") {\n+ app.rpc.updateMessageText(this.messageUid, this.value);\n+ }\n+ }\n+\n+ updateStatus() {\n+ if (this.liveType) {\n+ return;\n+ }\n+ if (this.trackSecondsBetweenEvents(this.lastUpdateEvent, new Date()) > 1) {\n+ this.lastUpdateEvent = new Date();\n+ if (typeof app !== \"undefined\" && app.rpc && typeof app.rpc.set_typing === \"function\") {\n+ app.rpc.set_typing(this.channelUid);\n+ }\n+ }\n+ }\n+\n+ update() {\n+ const expired = this.trackSecondsBetweenEvents(this.lastChange, new Date()) >= this.liveTypeInterval;\n+ const changed = (this.value !== this.previousValue);\n+\n+ if (changed || expired) {\n+ this.lastChange = new Date();\n+ this.updateStatus();\n+ }\n+\n+ this.previousValue = this.value;\n+\n+ if (this.liveType && expired) {\n+ this.value = \"\";\n+ this.previousValue = \"\";\n+ this.messageUid = null;\n+ return;\n+ }\n+\n+ if (changed) {\n+ if (this.liveType) {\n+ this.updateMessage();\n+ }\n+ }\n+ }\n+\n+ async sendMessage(channelUid, value) {\n+ if (!value.trim()) {\n+ return null;\n+ }\n+ return await app.rpc.sendMessage(channelUid, value);\n+ }\n }\n \n-customElements.define('chat-input', ChatInputElement);\n+customElements.define('chat-input', ChatInputComponent);\ndiff --git a/src/snek/templates/app.html b/src/snek/templates/app.html\nindex 7baa67a..596b5d1 100644\n--- a/src/snek/templates/app.html\n+++ b/src/snek/templates/app.html\n@@ -18,6 +18,7 @@\n <script src=\"/file-manager.js\" type=\"module\"></script>\n <script src=\"/user-list.js\"></script>\n <script src=\"/message-list.js\" type=\"module\"></script>\n+ <script src=\"/chat-input.js\" type=\"module\"></script>\n <link rel=\"stylesheet\" href=\"/user-list.css\">\n \n <link rel=\"stylesheet\" href=\"/base.css\">\ndiff --git a/src/snek/templates/web.html b/src/snek/templates/web.html\nindex 02e60df..94d0ac5 100644\n--- a/src/snek/templates/web.html\n+++ b/src/snek/templates/web.html\n@@ -4,10 +4,6 @@\n \n {% block main %}\n \n-\n-\n-\n-\n <section class=\"chat-area\">\n <message-list class=\"chat-messages\">\n {% for message in messages %}\n@@ -16,10 +12,7 @@\n {% endautoescape %}\n {% endfor %}\n </message-list>\n- <div class=\"chat-input\">\n- <textarea list=\"chat-input-autocomplete-items\" placeholder=\"Type a message...\" rows=\"2\" autocomplete=\"on\"></textarea>\n- <upload-button channel=\"{{ channel.uid.value }}\"></upload-button>\n- </div>\n+ <chat-input live-type=\"false\" channel=\"{{ channel.uid.value }}\"></chat-input>\n </section>\n {% include \"dialog_help.html\" %}\n {% include \"dialog_online.html\" %}\n@@ -27,11 +20,8 @@\n import { app } from \"/app.js\";\n import { Schedule } from \"/schedule.js\";\n const channelUid = \"{{ channel.uid.value }}\";\n-\n- function getInputField(){\n- return document.querySelector(\"textarea\")\n- }\n- getInputField().autoComplete = {\n+ const chatInputField = document.querySelector(\"chat-input\");\n+ chatInputField.autoCompletions = {\n \"/online\": () =>{\n showOnline();\n },\n@@ -39,117 +29,15 @@\n document.querySelector(\".chat-messages\").innerHTML = '';\n },\n \"/live\": () =>{\n- getInputField().liveType = !getInputField().liveType\n+ \n+ chatInputField.liveType = !chatInputField.liveType\n },\n \"/help\": () => {\n showHelp();\n }\n- }\n-\n-\n- function initInputField(textBox) {\n- if(textBox.liveType == undefined){\n- textBox.liveType = false\n- }\n- let typeTimeout = null;\n- textBox.addEventListener('keydown',async (e) => {\n- if(typeTimeout){\n- clearTimeout(typeTimeout)\n- typeTimeout = null\n- }\n- if(e.target.liveType){\n- typeTimeout = setTimeout(()=>{\n- e.target.lastMessageUid = null\n- e.target.value = ''\n- },3000)\n- }\n- if(e.key === \"ArrowUp\"){\n- const value = findDivAboveText(e.target.value).querySelector('.text')\n- e.target.value = value.textContent\n- console.info(\"HIERR\")\n- return\n- }\n- if (e.key === \"Tab\") {\n-\n- const message = e.target.value.trim();\n- if (!message) {\n- return\n- }\n- let autoCompleteHandler = null;\n- Object.keys(e.target.autoComplete).forEach((key)=>{\n- if(key.startsWith(message)){\n- if(autoCompleteHandler){\n- return \n- }\n- autoCompleteHandler = key\n- }\n- })\n- if(autoCompleteHandler){\n- e.preventDefault();\n- e.target.value = autoCompleteHandler;\n- return\n- }\n- }\n- if (e.key === 'Enter' && !e.shiftKey) {\n- e.preventDefault();\n- const message = e.target.value.trim();\n- if (!message) {\n- return\n- }\n- let autoCompleteHandler = e.target.autoComplete[message]\n- if(autoCompleteHandler){\n- const value = message;\n- e.target.value = '';\n- autoCompleteHandler(value)\n- return\n- }\n-\n- e.target.value = '';\n- if(textBox.liveType && textBox.lastMessageUid && textBox.lastMessageUid != '?'){\n- \n- \n- app.rpc.updateMessageText(textBox.lastMessageUid, message)\n- textBox.lastMessageUid = null\n- return \n- }\n-\n- const messageResponse = await app.rpc.sendMessage(channelUid, message);\n- \n-\t }else{\n-\t\tif(textBox.liveType){\n- if(e.target.value.endsWith(\"\\n\") || e.target.value.endsWith(\" \")){\n- return\n- } \n- if(e.target.value[0] == \"/\"){\n- return\n- }\n- if(!textBox.lastMessageUid){\n- textBox.lastMessageUid = '?'\n- app.rpc.sendMessage(channelUid, e.target.value).then((messageResponse)=>{\n- textBox.lastMessageUid = messageResponse\n- })\n- }\n- if(textBox.lastMessageUid == '?'){\n- return;\n- }\n- app.rpc.updateMessageText(textBox.lastMessageUid, e.target.value)\n- }else{\n- app.rpc.set_typing(channelUid)\n- }\n-\n- \n-\t }\n- });\n- document.querySelector(\"upload-button\").addEventListener(\"upload\",function(e){\n- getInputField().focus();\n- })\n- document.querySelector(\"upload-button\").addEventListener(\"uploaded\",function(e){\n- let message = \"\"\n- e.detail.files.forEach((file)=>{\n- message += `[${file.name}](/channel/attachment/${file.relative_url})`\n- })\n- app.rpc.sendMessage(channelUid,message)\n- })\n+ }\n+ \n+ const textBox = document.querySelector(\"chat-input\").textarea\n textBox.addEventListener(\"paste\", async (e) => {\n try {\n const clipboardItems = await navigator.clipboard.read();\n@@ -168,7 +56,7 @@\n }\n \n if (dt.items.length > 0) {\n- const uploadButton = document.querySelector(\"upload-button\");\n+ const uploadButton = chatInputField.uploadButton\n const input = uploadButton.shadowRoot.querySelector('.file-input')\n input.files = dt.files;\n \n@@ -187,7 +75,7 @@\n \n const dt = e.dataTransfer;\n if (dt.items.length > 0) {\n- const uploadButton = document.querySelector(\"upload-button\");\n+ const uploadButton = chatInputField.uploadButton\n const input = uploadButton.shadowRoot.querySelector('.file-input')\n input.files = dt.files;\n \n@@ -197,13 +85,16 @@\n chatInput.addEventListener(\"dragover\", async (e) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = \"link\";\n+ \n+\n })\n \n- textBox.focus();\n- }\n+ chatInputField.textarea.focus();\n+\n+ \n \n function replyMessage(message) {\n- const field = getInputField()\n+ const field = chatInputField\n field.value = \"```markdown\\n> \" + (message || '') + \"\\n```\\n\";\n field.focus();\n }\n@@ -294,8 +185,8 @@\n lastMessage = messagesContainer.querySelector(\".message:last-child\");\n if (doScrollDown) {\n lastMessage?.scrollIntoView({ block: \"end\", inline: \"nearest\" });\n- const inputBox = document.querySelector(\".chat-input\");\n- inputBox.scrollIntoView({ block: \"end\", inline: \"nearest\" });\n+ \n+ chatInputField.scrollIntoView({ block: \"end\", inline: \"nearest\" });\n }\n }\n \n@@ -378,17 +269,17 @@\n messagesContainer.querySelector(\".message:last-child\").scrollIntoView({ block: \"end\", inline: \"nearest\" });\n setTimeout(() => {\n \n- getInputField().focus();\n+ chatInputField.focus();\n },500)\n \n }\n }\n if (event.shiftKey && event.key === 'G') {\n- if(document.activeElement != getInputField()){\n+ if(chatInputField.isActive()){\n \n updateLayout(true);\n setTimeout(() => {\n- getInputField().focus();\n+ chatInputField.focus();\n },500)\n }\n \n@@ -432,7 +323,6 @@\n document.body.removeChild(overlay);\n });\n });\n- initInputField(getInputField());\n updateLayout(true);\n </script>\n {% endblock %}"} {"repo": ".", "date": "2025-05-17", "line": "feat: Added star animation to sandbox page", "commit": "e79abf4a26454cddf766cd1ba138554817c820cb", "diff": "commit e79abf4a26454cddf766cd1ba138554817c820cb\nAuthor: retoor <retoor@molodetz.nl>\nDate: Sat May 17 17:46:59 2025 +0200\n\n Update stars.\n\ndiff --git a/src/snek/static/sandbox.css b/src/snek/static/sandbox.css\nnew file mode 100644\nindex 0000000..1419fe4\n--- /dev/null\n+++ b/src/snek/static/sandbox.css\n@@ -0,0 +1,28 @@\n+ .star {\n+ position: absolute;\n+ width: 2px;\n+ height: 2px;\n+ border-radius: 50%;\n+ opacity: 0;\n+ animation: twinkle ease-in-out infinite;\n+ }\n+\n+ @keyframes twinkle {\n+ 0%, 100% { opacity: 0; }\n+ 50% { opacity: 1; }\n+ }\n+\n+ .content {\n+ position: relative;\n+ z-index: 1;\n+ font-family: sans-serif;\n+ text-align: center;\n+ top: 40%;\n+ transform: translateY(-40%);\n+ }\n+\ndiff --git a/src/snek/templates/app.html b/src/snek/templates/app.html\nindex 596b5d1..ceef196 100644\n--- a/src/snek/templates/app.html\n+++ b/src/snek/templates/app.html\n@@ -19,6 +19,7 @@\n <script src=\"/user-list.js\"></script>\n <script src=\"/message-list.js\" type=\"module\"></script>\n <script src=\"/chat-input.js\" type=\"module\"></script>\n+ <link rel=\"stylesheet\" href=\"/sandbox.css\">\n <link rel=\"stylesheet\" href=\"/user-list.css\">\n \n <link rel=\"stylesheet\" href=\"/base.css\">\n@@ -78,5 +79,6 @@ let installPrompt = null\n \n ;\n </script>\n+ {% include \"sandbox.html\" %}\n </body>\n </html>\ndiff --git a/src/snek/templates/sandbox.html b/src/snek/templates/sandbox.html\nnew file mode 100644\nindex 0000000..6fd9b3f\n--- /dev/null\n+++ b/src/snek/templates/sandbox.html\n@@ -0,0 +1,31 @@\n+\n+\t\t\t\t\t\t\t<script>\n+ \t\n+ const STAR_COUNT = 200;\n+ const body = document.body;\n+\n+ for (let i = 0; i < STAR_COUNT; i++) {\n+ const star = document.createElement('div');\n+ star.classList.add('star');\n+\n+ star.style.left = Math.random() * 100 + '%';\n+ star.style.top = Math.random() * 100 + '%';\n+\n+ star.style.width = size + 'px';\n+ star.style.height = size + 'px';\n+\n+ star.style.animationDuration = duration + 's';\n+ star.style.animationDelay = delay + 's';\n+\n+ body.appendChild(star);\n+ }\n+ \n+\n+\t\t\t\t\t\t\t</script>"} +{"repo": ".", "date": "2025-01-17", "line": "feat: Initial project setup with basic structure and boilerplate files", "commit": "66f89429366042c77599f3a9b8c1a7aecf976a4f"} +{"repo": ".", "date": "2025-01-17", "line": "feat: Initialized project with basic description and setup instructions", "commit": "46a27405aeb8ec426fd1c686a2c090f9fe9c0e62"} +{"repo": ".", "date": "2025-01-18", "line": "feat: Added basic login and register pages with styling.", "commit": "a7446d131413da9f013a56d3541192d8ab1e22b0"} +{"repo": ".", "date": "2025-01-18", "line": "docs(docker): Add restart policy to snek service", "commit": "2e3b85d7f739160783e7c5552f1306298047704a"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Refactor project structure and introduce generic form components.", "commit": "ba83922660dade77dcb96e8ba9c73cfcba8c2b81"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Complete system with basic functionality and initial views", "commit": "d20079f3ed8f261bcda0f5379f4c9e23ee941527"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Use \"/back\" URL for back button navigation", "commit": "4b48485bcc9f73662a1eb4ab3142bb0f4d5bef7a"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Implement size attribute for fancy-button", "commit": "0271e3f9719a4155fc5be37c36feca865932b0c1"} +{"repo": ".", "date": "2025-01-24", "line": "fix: Updated button URLs in index.html", "commit": "bda93e354f4691483dbbef29949672ab0989b7e0"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Style adjustments and layout improvements for responsive design", "commit": "757b67b78c2f396df7ac7b5706f98833aedfb85b"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Refactor styling and layout for improved aesthetics and responsiveness", "commit": "6ba6121988dae019d4c4c0a3b8592b443f094065"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Refactor login and register form endpoints to use /login.json and /register.json", "commit": "c1eeacc0b415ef770418bb053d18ac0ffa4f64c2"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Implemented caching decorator for asynchronous functions", "commit": "21ab5628b072320ae0851819116e57539b2397d9"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Switch to gunicorn and add docs and about pages", "commit": "8486c22c325ba358bd48766c518f7c7bd30059eb"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Added API documentation with code examples and HTML template", "commit": "aecd9f844ef0a277a55aa536db3336362e8db353"} +{"repo": ".", "date": "2025-01-24", "line": "docs: Added styling for dialog element", "commit": "be9489f939b3518f9c3a73b9e54ba0f9d34ae24c"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Implemented user registration with username availability check and email field.", "commit": "2ba55f692dfc1b60ac55d514b182fb8834cb99bb"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Add documentation subapp and markdown extension", "commit": "18b76ebd5e2f11451db04800d426a16b1ef1dd14"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Refactor form validation and rendering for improved consistency", "commit": "9b93403a93ac0b03a57fb5dc10db5c35349c4d6f"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Added documentation and basic API examples", "commit": "b56371994f5d3f5c1aa5d63c28efd18856ea8e9b"} +{"repo": ".", "date": "2025-01-24", "line": "feat: Improve document handling and error responses", "commit": "dae877113c76b6f0eded7b2d63ef921123a2b559"} +{"repo": ".", "date": "2025-01-25", "line": "feat: Added session support and login functionality", "commit": "5c69e14d7cfae8da4efab776165cc8e466edcc41"} +{"repo": ".", "date": "2025-01-25", "line": "feat: Implemented status endpoint with user information", "commit": "352d2deb12a471bc90425961849fb2e92da3ab16"} +{"repo": ".", "date": "2025-01-25", "line": "feat: Refactor session management and improve status endpoint", "commit": "12ca8e4296ca9693276422e524d7061685556ba0"} +{"repo": ".", "date": "2025-01-25", "line": "feat: Added logout functionality and improved login form validation", "commit": "bb6bcf41d1bb2132684b6251853f7d34e202a9f7"} +{"repo": ".", "date": "2025-01-25", "line": "feat: Refactor service and mapper setup, introduce Cache and Object\n\nThis commit refactors the service and mapper setup to utilize a more structured approach with SimpleNamespace and Object. It also introduces a Cache class for caching frequently accessed data and a BaseObject class for managing object attributes. Additionally, new mappers and models for channels, channel members, and channel messages have been added to support the new features.", "commit": "b4f9ff2c628ffd5aafdfbc4a403b2b71fa0110c8"} +{"repo": ".", "date": "2025-01-25", "line": "feat: Refactor model and service imports for clarity", "commit": "f25feeeca3502eee94554e7152ca7ca946115053"} +{"repo": ".", "date": "2025-01-26", "line": "feat: Added RPC view and WebSocket support for real-time communication.", "commit": "488afdcc747df9593273f652b17b5fe8db07b1df"} +{"repo": ".", "date": "2025-01-26", "line": "refactor: Minor code formatting and whitespace adjustments", "commit": "4c601e8333b3a462c63ab6e02b73b9f5306b4a58"} +{"repo": ".", "date": "2025-01-26", "line": "feat: Implemented basic chat functionality with message sending and display", "commit": "4ae846cf8b4f1158ac47ce2825d37e03e9b6677f"} +{"repo": ".", "date": "2025-01-26", "line": "feat: Use dynamic websocket URL based on environment", "commit": "fb7cb35921b73fd22a4ef045fe23b8dab87a7af4"} +{"repo": ".", "date": "2025-01-27", "line": "feat: Improve WebSocket connection handling and UI\n\nThis commit introduces several improvements:\n\n- Added error handling to WebSocket send operations in `SocketService`.\n- Updated the WebSocket URL in `app.js` to include the port number.\n- Implemented `query` methods in `BaseMapper` and `BaseService` for database queries.\n- Added a `chat-window` component and updated the HTML structure for a better chat interface.\n- Implemented `get_messages` method in `RPCView` to fetch messages from the database.\n- Added `get_channels` method in `RPCView` to fetch channels.", "commit": "36c69eb8bb35068faebd396af1375fe5927eec44"} +{"repo": ".", "date": "2025-01-27", "line": "feat: Initial chat window component with channel loading", "commit": "87895a72d3ddb5f3ca98e4409f251e663e6dd688"} +{"repo": ".", "date": "2025-01-27", "line": "feat: Added snek.d* to .gitignore and configured database path", "commit": "aec9ffd1a1a49acad8940b793be6ff3abcae07a3"} +{"repo": ".", "date": "2025-01-27", "line": "feat: Added notification service and related mappings and services.", "commit": "4f71f745744b1a413a729875bc42366ea3ab665d"} +{"repo": ".", "date": "2025-01-27", "line": "feat: Implement chat input component with basic functionality", "commit": "2a3e225e1dbb40374e841af8977ff19cd4711f0c"} +{"repo": ".", "date": "2025-01-27", "line": "fix: Use user uid from database after login", "commit": "188a1e61783a7d08cb7ece1fbcd332aa1f19672a"} +{"repo": ".", "date": "2025-01-27", "line": "fix: Disable debug logging and remove unnecessary console statements", "commit": "26210f8c09c81f4ff4f7ed796d5d8bcd6d8b639e"} +{"repo": ".", "date": "2025-01-27", "line": "fix: Removed console logs and initial message", "commit": "095e30a92f6d12edf16ca87d66b335088b853490"} +{"repo": ".", "date": "2025-01-27", "line": "chore: Remove unnecessary benchmark script", "commit": "374db23669e203c98e5335b9a7abe9aff2110537"} +{"repo": ".", "date": "2025-01-27", "line": "feat: Improved cache logging and socket cleanup", "commit": "f3d12a257e7a43e3292654d7f67f05d823f16283"} +{"repo": ".", "date": "2025-01-27", "line": "chore: Remove generated pycache file", "commit": "8e825a90c6e575f114b380312bb9c5726577b8b7"} +{"repo": ".", "date": "2025-01-28", "line": "feat: Limit table results to 30", "commit": "01d8093e7210910016ea5d6d8bbc5d8f2514c14d"} +{"repo": ".", "date": "2025-01-28", "line": "feat: Limit message retrieval to 30 entries", "commit": "d93d48ef7e023c62bfa9b64ede20cd9f86c3242e"} +{"repo": ".", "date": "2025-01-28", "line": "refactor: Improved message handling and added scheduling for event dispatch", "commit": "da72a15068fe14eeb2b50b4cd3342fb4b70b0c79"} +{"repo": ".", "date": "2025-01-28", "line": "feat: Added schedule functionality and minor UI adjustments", "commit": "99d335ac244c2258d82821344fa517857a782f4a"} +{"repo": ".", "date": "2025-01-28", "line": "feat: Added schedule benchmarking with custom messages", "commit": "4f1a48c197fcad25d80873bac55cf66f7ff99382"} +{"repo": ".", "date": "2025-01-28", "line": "feat: Added notification sound on new message", "commit": "5aee606d5d65e71afa8366d24ed4632f662a9126"} +{"repo": ".", "date": "2025-01-28", "line": "feat: Added notification sound and improved chat input functionality", "commit": "14c59ba5c0abc7d1331e022cc99222223ea21526"} +{"repo": ".", "date": "2025-01-28", "line": "feat: Improved connection handling and PWA support", "commit": "b2ca373081bdd7514b0f849dc1033edfd3f76424"} +{"repo": ".", "date": "2025-01-28", "line": "feat: Added favicon and manifest for PWA support", "commit": "7d05bd9da45489c02a9b057eef86d45e2ca90049"} +{"repo": ".", "date": "2025-01-28", "line": "docs: Added display and start_url to manifest", "commit": "4da635502bca60efd0cc59aa4df236d7b99c2ec2"} +{"repo": ".", "date": "2025-01-28", "line": "refactor: Reduced padding in chat messages", "commit": "d69c75c6197e857ad61e4dbc872b5ab5872c4837"} +{"repo": ".", "date": "2025-01-28", "line": "feat: Add data attributes to message elements", "commit": "9e94210bc3f3b1b614a198591c52f404d84a8be2"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added user color and updated message display", "commit": "84e5bac1b93d5d1c124d303e6b08a29baaf4977c"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Add utility service for generating random light hex colors", "commit": "284d38096c7c5b1201f261ec7a5a28ed457952b5"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added avatar color and text color", "commit": "93b2f6cc41f08e21241642976b90e3dd98dc37ec"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Add linkify functionality to message text", "commit": "9f652ece1bf0498f9032f94b77becc96b6eff009"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Increased result limit to 60", "commit": "16afbb4e15f370babeedfc2aa917daa0292da5a6"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Hide avatar and author until switch-user message", "commit": "41927b7ef439424326cc58e3939f476e04b8eabb"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Add padding and switch-user class for message differentiation", "commit": "75ec590be5fc3f446c97549d90c135966142ac25"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Show message time on last message and switch user messages", "commit": "0e821f8b588def99f950fecb9369456cff086e0b"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Ensure HTTPS for external URLs", "commit": "931aae5134cad80bf7f5ba87fe215a03761f081b"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added html-frame.js script tag", "commit": "c558dc2d79b90e7424cf4311747f077332b0a193"} +{"repo": ".", "date": "2025-01-29", "line": "fix: Corrected typo in script source path", "commit": "5f3dac8bc6b702735383688de44ad7609264742a"} +{"repo": ".", "date": "2025-01-29", "line": "fix: Ensure URL is HTTPS and handle relative URLs", "commit": "4442f75ec50d3d27cfae1702459d5f8f34ba415b"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added padding and URL handling for HTMLFrame", "commit": "030942db0984ac0f3a4072581d58d81fad03ef91"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added install prompt and button for PWA installation", "commit": "438fad301447e3265ff7484606f8222b271e4d9d"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added install button to navigation", "commit": "3e4b6b00620f8cf2c8b8c63918e6c93d2987174d"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Hide install button initially", "commit": "1f5dc57d6f24b17fa66ab5692038e005cc444378"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Render messages with HTML for rich formatting", "commit": "03c72e85f72207a7b2480f881f7b0cb7055c5feb"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added message template with markdown support and styling", "commit": "561a915e30274d8b191678135912313ebccde70f"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Refactor project to snekbak", "commit": "d7c003c4096f8cfed8f4edd517f41d45f4f8b501"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Refactor project from snekbak to snek", "commit": "f9fed90e861d8bc5ae5bcd89cb07bd67a1e66a98"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added documentation and initial form API support", "commit": "b562d171674c2f75592ff3a0dd25b51d2a2457db"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added cache directory to .gitignore", "commit": "82de0f304469e6214169a2bdcf9c65673baa9e76"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added docks to message template", "commit": "80f1bbc05e612c45fb2ccbb629a6aa3b468c627e"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added snek image", "commit": "9e89e27c6688b0e05e4a10a0538d599f82278e64"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added notification audio and scheduling functionality", "commit": "af399e3b72c772ed97e943e7d71dc6384ab8ccc0"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added Docker configuration for deployment", "commit": "3be25285f4f0afaaf991ee7cc0a8f71854e8de4c"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Enable markdown rendering in message display", "commit": "75cb7605cd5e8e91cab2ffbc9000eb5987e40136"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Render markdown messages with raw HTML support", "commit": "4fbfe90a1309ec7bf7bf1d19465a3fc441aaddc5"} +{"repo": ".", "date": "2025-01-29", "line": "chore: Remove compiled python files", "commit": "f69586ccf7975be0bdd24659d6acec068f5183d6"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added requests dependency and template extensions for linkification and python execution.", "commit": "bca39a612cad5f340864a4dc62d94cda962985f9"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Style message images and add time ago functionality", "commit": "5b88350ff27b526c5e4ee938d0665d3a4e1b5b5c"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Improve time display and linkify URLs in messages", "commit": "20d8d27f03e87bf06515d0664a00e669b92df49f"} +{"repo": ".", "date": "2025-01-29", "line": "fix: Prevent linkify_https from processing non-https text", "commit": "3d6e1d2e943baabaf0b0875284bf18132bc3967a"} +{"repo": ".", "date": "2025-01-29", "line": "refactor: Removed unused padding rule in base.css", "commit": "5c4c5793899776e5d369f3949b4a8142a68ba7ee"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Display install button inline-block", "commit": "a8e3ad1af9f683ad25730ff48180c5306f72e1f6"} +{"repo": ".", "date": "2025-01-29", "line": "feat: Added username to chat messages", "commit": "99cea506de5ea0c5b373869b7c28d965b8af55e6"} +{"repo": ".", "date": "2025-01-29", "line": "fix: Corrected calculation in timeAgo function", "commit": "03e90039695abc0fdc9276980bd8728bd8951f05"} +{"repo": ".", "date": "2025-01-31", "line": "feat: Added SSH service and basic gallery styling", "commit": "b06a10f6eca08f312c4f53fac36a4a8dcd9d91b5"} +{"repo": ".", "date": "2025-01-31", "line": "feat: Initialized SFTP server with basic authentication and file serving.", "commit": "15de277a5be330fe6962e5271c537e3b5ef40de4"} +{"repo": ".", "date": "2025-01-31", "line": "feat: Enable unbuffered python output", "commit": "8eff6dd6cb7a8ccf866f8f98d22d3aea59c572f6"} +{"repo": ".", "date": "2025-01-31", "line": "feat: Added logging for response messages", "commit": "4de93489ef01bf070f461915989be611156121dd"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Improve socket error logging and flushing", "commit": "1c53a90e00bd5ec8eacfeaeb386516cb470b8b3d"} +{"repo": ".", "date": "2025-01-31", "line": "feat: Added logging for incoming websocket messages", "commit": "312b9eeecaee5d16247a7f0c694e3168893c389d"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Simplify error handling in RPCView", "commit": "c6f43931664c01c597c642e35c64ec49f3008101"} +{"repo": ".", "date": "2025-01-31", "line": "refactor: Remove debug print statement", "commit": "5fd03efc301d722a5ee09c8b0cef6d04c1130fd3"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Handle WebSocket close events and improve error handling", "commit": "780c178d95a6dbe3fbd6b2fac18a6bdb16ec0b64"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Added logging for RPC exceptions", "commit": "cc3b896d2cd80affc251434800844829cc3fb6e1"} +{"repo": ".", "date": "2025-01-31", "line": "refactor: Use internal send method for RPC responses", "commit": "0a70e80668a598c909674c78b654b5ad8e6afce5"} +{"repo": ".", "date": "2025-01-31", "line": "refactor: Buffered RPCView methods", "commit": "bfdfa6c8bb27be4bd83bf8d4e3084e37ef0f7fae"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Use `send_str` instead of `send_text` for JSON serialization", "commit": "010f3b03a0983843c74219484e78c50d595da6e7"} +{"repo": ".", "date": "2025-01-31", "line": "feat: Import json for RPC handling", "commit": "8f502af84eea60b5349fd1980d352f0f8e001502"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Ensure messages are flushed to console", "commit": "10c7232a8f6378eed8f5b4adecca8d582d57a069"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Prevent premature socket deletion on error/close", "commit": "88749ce05c7c4e9b5e238d16cb9fa4053f092fc1"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Unbuffered websocket subscriptions", "commit": "2ae2e8450cad47031067f3baae3b09ff521c5c87"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Removed unnecessary style block in message template", "commit": "495543144d464121af0afab6545a5267ad561a57"} +{"repo": ".", "date": "2025-01-31", "line": "feat: Added highlight styles to web.html", "commit": "cfd3e7881eca77d10d32de2440a9d2b03aeaea96"} +{"repo": ".", "date": "2025-01-31", "line": "refactor: Streamlined message template rendering", "commit": "efe12644eda127170a3d60e086fa31ed940fca6e"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Prevent default event on keyup and change events", "commit": "7526bcc816ffb759e3708f30167b4d3367955b64"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Prevent form submission on Shift+Enter in chat input", "commit": "1999a6c8d8dd4fdbd48d5553a1704dfa065275ee"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Prevent empty messages from being submitted", "commit": "ae5fffe5e0faf948a22feea0e651e08a0ed559fb"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Prevent submitting empty messages", "commit": "663ab415101e5da31fb71e3e9e3b433fbd6c3031"} +{"repo": ".", "date": "2025-01-31", "line": "feat: Added query endpoint with security checks", "commit": "3796c7c54767b5de18c5310d20c9dd3c5aafdd0c"} +{"repo": ".", "date": "2025-01-31", "line": "feat: Made channel query asynchronous", "commit": "f6f99684307249b6650dcbbb3168db1ebfa71e73"} +{"repo": ".", "date": "2025-01-31", "line": "fix: Disable autoescape before linkify and markdown", "commit": "0c68c4e62255a307ecb48cba011ef38ace935eb3"} +{"repo": ".", "date": "2025-02-01", "line": "feat: Allow underscores and plus signs in usernames", "commit": "4185bb3a69ac66d7b6614acf76bb5a2f613e0b82"} +{"repo": ".", "date": "2025-02-01", "line": "feat: Added emoji support to templates and app.py", "commit": "928969b8b6266298317ea4f7ca3e6b2cfbd42e82"} +{"repo": ".", "date": "2025-02-01", "line": "refactor: Removed unnecessary timezone handling", "commit": "feeb94c9cf08ebee6d42165988b1d51030df4c33"} +{"repo": ".", "date": "2025-02-01", "line": "feat: Added highlight stylesheet link", "commit": "e0ed4491b414c51b54e4c3ebd10cbebb46a903c6"} +{"repo": ".", "date": "2025-02-01", "line": "feat: Add basic syntax highlighting CSS", "commit": "98d89dbc5f45a61ab6335e38d6e4a1df39bcc621"} +{"repo": ".", "date": "2025-02-01", "line": "feat: Applied highlight.css to message templates", "commit": "a06e3f404a15d8115fa65ba8533ff7774baa0beb"} +{"repo": ".", "date": "2025-02-02", "line": "feat: Add user data and audio notification check", "commit": "99fc9118b37f8564cd6e211d3d77ef997592f361"} +{"repo": ".", "date": "2025-02-02", "line": "fix: Resolve issue with user data initialization", "commit": "7d750db1f8235c8231699c2da39c1075ac678841"} +{"repo": ".", "date": "2025-02-03", "line": "feat: Improved code display with word wrapping and line breaks", "commit": "3ae43c84e768a712fd2d0a8e65f52edd86bfa6a5"} +{"repo": ".", "date": "2025-02-03", "line": "style: Improved text wrapping in base.css", "commit": "23c8ebca73ac49c826434d40fd1e1fd2e3435957"} +{"repo": ".", "date": "2025-02-03", "line": "style: Improved text wrapping in message content", "commit": "38a24e9a12355f93776c9aef0b9caa5afb075531"} +{"repo": ".", "date": "2025-02-03", "line": "feat: Commented out padding in message list for testing", "commit": "83cc0f613708ed27b928ba45b330c984d82dd546"} +{"repo": ".", "date": "2025-02-03", "line": "feat: Added padding to message list and hid avatar", "commit": "079187e1b460e5554bfec8b9658b5059cc3d51c6"} +{"repo": ".", "date": "2025-02-03", "line": "feat: Hide avatar on message list", "commit": "f4a5536dcf1e27a7e8319488f8f39a8acfb818a2"} +{"repo": ".", "date": "2025-02-03", "line": "revert: Restored avatar visibility", "commit": "fe707dca4ea0bc2ecaedcda292f1ae636fce2b93"} +{"repo": ".", "date": "2025-02-03", "line": "feat: Added upload button functionality to the chat interface.", "commit": "b48a901e3385617d36511e251c4e7c62498e23bc"} +{"repo": ".", "date": "2025-02-03", "line": "fix: Remove unnecessary whitespace and improve text wrapping", "commit": "f395d1617394045cac7c41af0cd5ce9d6ef55ed8"} +{"repo": ".", "date": "2025-02-03", "line": "refactor: Removed setup.cfg and adjusted code for improved stability", "commit": "084f8dba2075aec93d9d88fd7cdd7f67fc63a212"} +{"repo": ".", "date": "2025-02-04", "line": "feat: Added drive service with upload functionality", "commit": "6f9adfe67fd551dd99746c40bb55706a7ffcef3b"} +{"repo": ".", "date": "2025-02-05", "line": "feat: Added :snek1: emoji support", "commit": "b6185a95f3fcbf539ec0ba767d4c0923092f8e82"} +{"repo": ".", "date": "2025-02-06", "line": "fix: Handle failed RPC calls and track success status", "commit": "203314b209030f297cd888685bb68721bc21c61b"} +{"repo": ".", "date": "2025-02-07", "line": "fix: Correctly detect code language or fallback to bash", "commit": "386d9c3aaee80115241866ae72df9fad3ea3c714"} +{"repo": ".", "date": "2025-02-07", "line": "fix: Handle empty code highlighting results", "commit": "d4aaa2d66be0a568eff8caf5ecef3c5826e6c67e"} +{"repo": ".", "date": "2025-02-07", "line": "fix: Escape code blocks in markdown renderer", "commit": "a301e2c5bfb8286f63a48c2860162780f95e820d"} +{"repo": ".", "date": "2025-02-07", "line": "fix: Updated html.parser import", "commit": "cfa2af61b81b613bf2b8177b8acff1ea8b7c8576"} +{"repo": ".", "date": "2025-02-07", "line": "feat: Add code highlighting with lexer selection", "commit": "51f1b1d86e4813c10e2750f0771c1bdcc1274bfb"} +{"repo": ".", "date": "2025-02-07", "line": "feat: Replaced navigation links with emojis", "commit": "9840c8eb03f969330583e9c3a7b28dbb5548f7d6"} +{"repo": ".", "date": "2025-02-07", "line": "fix: Prevent lingering websocket connections on disconnect", "commit": "7ca2bc5776213828a31c7fc237784a0a73c6f759"} +{"repo": ".", "date": "2025-02-08", "line": "fix: Updated username regex and added user search functionality", "commit": "f291c0f2e4081fde4ed55d6cc25fdcbb1952af70"} +{"repo": ".", "date": "2025-02-08", "line": "feat: Initial app template with basic structure and links", "commit": "fcb05903f3f583ce8532d65ee7edf1ad8df91df4"} +{"repo": ".", "date": "2025-02-08", "line": "fix: Relaxed username and password regex constraints", "commit": "8d0d709e18be0177b99f76f320eeb02b70bb41b0"} +{"repo": ".", "date": "2025-02-08", "line": "feat: Added user search functionality with HTML and API endpoint", "commit": "d7b943dc8c8f485c975730d6054e32e67db36c91"} +{"repo": ".", "date": "2025-02-08", "line": "feat: Implemented search user form", "commit": "49eb76dc8b93cd422a9fda40cece480a573b8524"} +{"repo": ".", "date": "2025-02-08", "line": "feat: Prevent potentially harmful queries via search form", "commit": "60ca3ec7918073a2fb3ebe81e9ea733225391d99"} +{"repo": ".", "date": "2025-02-08", "line": "feat: Style updates and search user form improvements", "commit": "5154811b29ced87375ac457fafeb25305f64a954"} +{"repo": ".", "date": "2025-02-08", "line": "style: Adjusted chat message container styling", "commit": "a8fea31a326c7b9868dde866be553bb9f84eee88"} +{"repo": ".", "date": "2025-02-08", "line": "feat: Optimize database performance with WAL mode", "commit": "06b539b8845c49ec6d2789876ef4069b8df77117"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Prevent text selection in UI elements", "commit": "b169fa4792e02303ea0f61e5d83b3993a8f72f05"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Prevent text selection on title and logo elements", "commit": "ad4847a78e2945fe4f57ae16262e0c2a91a804f5"} +{"repo": ".", "date": "2025-02-09", "line": "fix: Prevent text selection in navigation", "commit": "bda5cfd52d5272742d147e93b506b87eeee04e1e"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Refactor search user view and template name", "commit": "afa40ada778c5d4102bce19312456adca51f70d0"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Implemented user search functionality with basic UI", "commit": "a42c2bdf5d2cee53c16e8dc123fc4473107ef203"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Prevent SQL injection by enhancing query validation", "commit": "e2a8efe5caac1ffaa70d6d7dc55e4e6b9741a35f"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Added branding and updated view templates", "commit": "a3cec5bce0386c8c3012262aa4a582241786220d"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Added WebView and channel routing\n\nfix: Corrected form validation in LoginView", "commit": "78f9679f308016320b64cd49bb3552fb63d26d27"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Added time descriptions and user switching in chat window", "commit": "e7cd397e0fe98074833e08880d915516718adaf5"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Added custom scrollbar styling for chat messages", "commit": "feb5234b3b581936d45ac328b23de7da8f375ee2"} +{"repo": ".", "date": "2025-02-09", "line": "refactor: Improved chat message rendering and layout updates", "commit": "ecb77cf361f0d55b512028701c67c1e347836e6e"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Improved layout updates after message insertion", "commit": "bfca2bdf734c9b9522186c1ff1b6479f93f34658"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Refactor CSS for improved layout and styling", "commit": "83121f7fa99df690a3b9029556aa023226cf22ef"} +{"repo": ".", "date": "2025-02-09", "line": "fix: Correctly append message element to chat messages", "commit": "dc2a31abeec3a85dab3c29ec270ae9fcf5ff2797"} +{"repo": ".", "date": "2025-02-09", "line": "style: Improved text wrapping and code highlighting", "commit": "cef83aefe7a4d2b37b9d4067d7482d9660a2dcbd"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Improved message appending in chat", "commit": "a6555dc069b81c25ea6bd3f8f3e6132cb2a2ea29"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Improved message content styling", "commit": "661eba7161c1d869d73f04641878521fbdf8b72a"} +{"repo": ".", "date": "2025-02-09", "line": "style: Removed unnecessary block display from message content.", "commit": "e75836fe879e4f7e2a8bb34ed8ca901cc624ce05"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Improved message time display", "commit": "0f400a0b6aa04ffdfd8de1e26be3318a39a174a8"} +{"repo": ".", "date": "2025-02-09", "line": "feat: Enable CORS credentials", "commit": "49c0f932ab3e5705380a57cefa8da9ea7b9967d3"} +{"repo": ".", "date": "2025-02-10", "line": "feat: Implemented online status and ping functionality", "commit": "54c40c6b8586fbb9b2b639cd0c7aa1c72a6e53f1"} +{"repo": ".", "date": "2025-02-10", "line": "feat: Display online status for channel members", "commit": "688e7fbf0e8977f442edc41cea1ac2a06f1ece40"} +{"repo": ".", "date": "2025-02-10", "line": "feat: Increase online status timeout to 20 seconds", "commit": "48891c438694d37cd1b8338a2cc1f96f7647e77d"} +{"repo": ".", "date": "2025-02-10", "line": "feat: Include last ping in online user data", "commit": "087ab1a8a55ae58b52078dc5cb7de7db65132e84"} +{"repo": ".", "date": "2025-02-10", "line": "feat: Implement online user status check", "commit": "3f75c8d5f9a67e68cf311ac5b9c60f13a3aa6493"} +{"repo": ".", "date": "2025-02-11", "line": "feat: Added private chat functionality with DM creation", "commit": "8a59ddd210bb3ab3d29f9207afbf887988b528d9"} +{"repo": ".", "date": "2025-02-11", "line": "feat: Implemented private chat functionality", "commit": "ca463b79a88687a76d9fab851b8f2ffc7e071e81"} +{"repo": ".", "date": "2025-02-11", "line": "feat: Implemented private chat functionality and updated tag to lowercase", "commit": "8fe24f711cb3e796471145a086c4b72289e12e1a"} +{"repo": ".", "date": "2025-02-11", "line": "feat: Implement private chat redirection", "commit": "bfe4b351c1fa750c9d12d3ae880928cca0346bba"} +{"repo": ".", "date": "2025-02-11", "line": "feat: Implement private chat redirection", "commit": "be35a6caf07c51eaf79625ad914215bebb9b11c5"} +{"repo": ".", "date": "2025-02-11", "line": "feat: Added base URL property and file type handling for uploads", "commit": "2cfb8fe3085f6c592488e81b3acf2dbb6f0ac420"} +{"repo": ".", "date": "2025-02-11", "line": "feat: Display uploaded files as links instead of iframes", "commit": "2541fc536aa45630ec55297c58787686e4154fab"} +{"repo": ".", "date": "2025-02-11", "line": "feat: Added echo endpoint and noresponse return value", "commit": "b6eba608435be4d798e50328f9149a8768a5cc8e"} +{"repo": ".", "date": "2025-02-13", "line": "feat: Added channel list and updated templates", "commit": "3baa6e53df459c3816958fbcd4cc6d4bbd1a8fd0"} +{"repo": ".", "date": "2025-02-13", "line": "feat: Add channel list and improve DM user display", "commit": "37da903936e4ab85fae254421c356966991d53e4"} +{"repo": ".", "date": "2025-02-15", "line": "```\nrefactor: Improved socket communication and removed unnecessary prints\n```", "commit": "1f8ebf71d0c2f7f1460ba7a1b6113831e4148edb"} +{"repo": ".", "date": "2025-02-15", "line": "feat: Refactor socket handling and messaging for improved user management and broadcasting", "commit": "53be4b060a1fff9cf58c7224dc4522bb0cafa852"} +{"repo": ".", "date": "2025-02-15", "line": "feat: Add channel tag to RPC view data", "commit": "9c3abdec2613c4d492c363cca8a07882dd3d8135"} +{"repo": ".", "date": "2025-02-15", "line": "fix: Correct channel UID retrieval in RPCView", "commit": "d1396801c05688e15ad7f1082dab2576b9a2b011"} +{"repo": ".", "date": "2025-02-16", "line": "feat: Embed YouTube videos directly into the page", "commit": "7c4334fe7b5b7e6ba44627a4f084638af51dd44c"} +{"repo": ".", "date": "2025-02-16", "line": "feat: Embed media, images, and YouTube videos in links", "commit": "c463dc6dca38348f9a54189e0b6eff6f5a3eb9b2"} +{"repo": ".", "date": "2025-02-16", "line": "fix: Correctly linkify HTTPS URLs", "commit": "263595fc7e7f86ec5d34b967b52c3d0a57dbc5fc"} +{"repo": ".", "date": "2025-02-16", "line": "refactor: Remove unused YouTube embed code", "commit": "7bcc67c6d35484c0fb8ddf201ea5b23b533d99d3"} +{"repo": ".", "date": "2025-02-16", "line": "fix: Handle file extensions in upload URLs", "commit": "be956a13db0941008802701196ca5e3870ebf2aa"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Ensure images, videos, and iframes within messages are responsive", "commit": "ea4196af8f7d7e0c97c004a07817bdc1dac999f5"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Improved media handling in message content", "commit": "f28be3ba55cbe9c1b20f70b4e1e8e2668eb2388f"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Implement scroll to bottom on new message", "commit": "2e69ac5921c16ea0cda1a1b7c84dd63ff458db62"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Implement timestamped pagination and focus textbox.", "commit": "8c33bc63d6cc623d0782f14c96a42c612accbc75"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Implement infinite scrolling for chat messages", "commit": "477ca5917a59d3720a6a5ae01b307dec1a74cfaf"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Implement infinite scrolling for chat messages", "commit": "9e3b9ae326b6ec632f3280018ea68d1896645b9a"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Use offsetMessage timestamp for infinite scroll", "commit": "33bc695cda6bf5889d802129117ed59992b87143"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Corrected method name for fetching messages", "commit": "aa5703e62f891fa7db09f07e6ce060875f5990d3"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Corrected offset calculation for infinite scroll", "commit": "1792686531fb440d9f453422bfa648c890c255d1"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Adjust offset for infinite scroll", "commit": "3ee7c6d8024245933569fdaf3f99a71afd14fe8f"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Implement infinite scrolling for chat messages", "commit": "95a8a458420dd2ebdbd6f7c03bd58c649985933e"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Implement infinite scrolling for messages", "commit": "162f89f9d0f7e304d355dd8f626ce2430dd840bf"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Use first message timestamp for infinite scroll", "commit": "104ee277669ee2cd55eb97b674f7a2432d31bb5a"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Prevent loading extra messages when not scrolled past half", "commit": "60efe6ee8a158cd671cbd05c20c7d382d6dcbb3b"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Implement infinite scrolling based on scroll position", "commit": "2fb6be753efa7f2cc1e0183551a1ad655388b970"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Hide scrollbar in chat messages", "commit": "24cd378c9d8f857f4f28af1069a2f5523a6441d8"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Use scroll instead of auto for chat messages", "commit": "8b98935d11496d51a0007db4b78391dde7a69163"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Hide scrollbar and improve chat styling", "commit": "5b03ecda3f3f9ae515dd00a4e421255535a2f215"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Hide scrollbar in chat messages", "commit": "3230c9f93bf9c3ae1b27474eac1cfc35f626d387"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Improve infinite scroll trigger position", "commit": "6c58f4b26c628881aee5cbe0597ba489f705e42f"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Adjust scroll position threshold for infinite scrolling", "commit": "bc8a296223f3a2c6e07b126a78373aa5bb40399d"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Adjusted scroll position check for infinite scrolling", "commit": "c77d2fb782258f787c5b52e3b27c5a3b0d468903"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Adjusted scroll threshold for infinite scrolling", "commit": "2e86ca2a3f1a4a8c746eecb46038a216b9706cdf"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Disable extra scroll loading", "commit": "1a608d8cfb1a3dcef9591b214fc54904615148bf"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Enable infinite scroll when near the bottom", "commit": "f0d76bd46af06637a21526c58d97f4b4d57f87dd"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Disable scroll loading for now", "commit": "1b6ebf50080b0b86256e639031f36da92b8990b2"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Corrected infinite scroll trigger condition", "commit": "6bdc6a7347a492f155629458c9b277cc16e04666"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Corrected infinite scroll logic", "commit": "c042af8b800879ecfbb817089119aab75d839c32"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Implement infinite scrolling for messages", "commit": "bb2b4b61b49bf4ba38d75bcbb0d751961c49cfa3"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Corrected initial message retrieval for infinite scroll", "commit": "2595594c3a99f6613e8e2194977fb1707c9f8b98"} +{"repo": ".", "date": "2025-02-17", "line": "refactor: Added comments and improved message loading logic", "commit": "6555e4f8266b01963cfd660a4e175b01ab615c0c"} +{"repo": ".", "date": "2025-02-17", "line": "refactor: Remove unnecessary comments from web.html", "commit": "2ab4341d0099799a84c0df6d91de33e1c5f69470"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Update manifest and app.html for PWA support", "commit": "e21880b4f5fd15259e09c207ce54d8e86bd61ac7"} +{"repo": ".", "date": "2025-02-17", "line": "fix: Removed display_override from manifest", "commit": "6c7266f20403f1f190c8b41f22d653c041dbbc77"} +{"repo": ".", "date": "2025-02-17", "line": "feat: Added 192x192 icon to manifest", "commit": "c745f609976397de0fb0cf7dec80205239b44b87"} +{"repo": ".", "date": "2025-02-18", "line": "refactor: Switch to asyncio for application startup and debugging", "commit": "3ccbe8be5c604d2683cf55553d1f11c674f6b930"} +{"repo": ".", "date": "2025-02-18", "line": "fix: Standardize environment variables in compose.yml and add logging to app.py", "commit": "ebb520dd4a80b513d1eb6fc6ce90e6b46f905100"} +{"repo": ".", "date": "2025-02-18", "line": "feat: Integrated profiler for performance analysis", "commit": "c6620ad70afce9407c16793de8ab4fea35523d81"} +{"repo": ".", "date": "2025-02-18", "line": "fix: Sort profiler stats by query parameter", "commit": "91a21db89b6d7bc36b5525f9d3a07d1ebe2a4ad3"} +{"repo": ".", "date": "2025-02-18", "line": "fix: Corrected typo in profiler sorting", "commit": "60404c6fd31894f3fbb6ce31ba48f1750101748f"} +{"repo": ".", "date": "2025-02-19", "line": "feat: Added a1 emoji and long emoji", "commit": "736123c4aa313c51a7e0daee8cdd6dc7583547fd"} +{"repo": ".", "date": "2025-02-19", "line": "feat: Increased Gunicorn workers for improved performance", "commit": "2ad5a7b1f49704baf7b890fcfda7a87fddd456f7"} +{"repo": ".", "date": "2025-02-19", "line": "fix: Reduced Gunicorn workers to 1", "commit": "e06824f4ec703388b7d55beeb5f1b3ef12452226"} +{"repo": ".", "date": "2025-02-19", "line": "refactor: Increased Gunicorn workers and moved app instantiation to global scope", "commit": "821db3cb1a67c20a968ac1dd8ecc4263e511cf16"} +{"repo": ".", "date": "2025-02-20", "line": "feat: Improved database indexing and UI enhancements\n\nThis commit introduces database indexing for improved query performance and several UI enhancements:\n\n- Added indexes to `user`, `channel_member`, and `channel_message` tables.\n- Updated CSS to include a container with improved styling for lists and links.\n- Modified `manifest.json` to set the scope to `/`.\n- Refactored `template.py` to handle image embedding and YouTube links more robustly.\n- Adjusted `app.html` to display \"Channels\" instead of \"Chat Rooms\".\n- Enhanced `search_user.html` with a container and improved styling.\n- Sanitized user data in `rpc.py` to remove sensitive information like email, password, message, and html.\n", "commit": "3623286a9dfba330612c42e579abcca63ab186ed"} +{"repo": ".", "date": "2025-02-20", "line": "refactor: Reduced gunicorn workers in compose file", "commit": "a7e0e5a3f821d51eb4e2ecde82baeb8ee0e183c7"} +{"repo": ".", "date": "2025-02-21", "line": "feat: Added sound effects for mentions", "commit": "54920e1545ffc68e2f928d3d042f5f11080f0d41"} +{"repo": ".", "date": "2025-02-21", "line": "feat: Add notification sounds for different events", "commit": "8ea41bb592b86e2f49b2f838e03006bc04472da5"} +{"repo": ".", "date": "2025-02-22", "line": "refactor: Moved sidebar channels to separate template and added channel notification", "commit": "fbe95d6631dfac2edc4c8600922020be4e15eccb"} +{"repo": ".", "date": "2025-02-22", "line": "feat: Add channel sidebar with message counts and highlighting", "commit": "076fbb30fb51ecfb5b15d394c760f55dac26e1c1"} +{"repo": ".", "date": "2025-02-26", "line": "feat: Added avatar support and updated message view to display avatars", "commit": "5af4e5754b6902ae13c798d9793281d62b684590"} +{"repo": ".", "date": "2025-02-26", "line": "feat: Generate unique avatar ID when requested", "commit": "e280e8776457605bbb5548fe9de5328b7b04bb8a"} +{"repo": ".", "date": "2025-02-26", "line": "feat: Generate unique avatars", "commit": "162cfe394558a085894a11cea57b193d6108b90e"} +{"repo": ".", "date": "2025-02-26", "line": "feat: Updated welcome message and registration buttons", "commit": "da1be6301c79cf76383e6568f91ee23bdf5119f6"} +{"repo": ".", "date": "2025-02-28", "line": "fix: Disable login requirement for avatar view", "commit": "66b85d146abac25df83edc1975db209b9d43fae7"} +{"repo": ".", "date": "2025-03-02", "line": "feat: Add last_message_on to ChannelModel and update on message send", "commit": "4620ebb800b5dd848ec28713f1afa20416698922"} +{"repo": ".", "date": "2025-03-02", "line": "feat: Add index creation with error handling", "commit": "e469e27abfedc0b08b483e0715b4dc9b16240c5e"} +{"repo": ".", "date": "2025-03-03", "line": "fix: Correctly handle trailing commas in link targets and improve upload view display", "commit": "45e3239cc06cdab0a8e5c1c1ef56593f65e750ea"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Implement push notifications with service worker", "commit": "c3c94461c295ef4c6219051369472f983267437c"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Add notification read functionality and unread stats", "commit": "578c182f2707a5f5b7c93f421e2035f7271aa60c"} +{"repo": ".", "date": "2025-03-05", "line": "fix: Handle missing notification model in mark_as_read", "commit": "580ec5ab0d57a542ab38b25a6c508804d5bcfa21"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Add new_count to ChannelMember and update notification service", "commit": "84d7b11f24b37cdc41ce9d5bb24be4080af14be9"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Display new notification count in RPC view", "commit": "d7851e645785fd707a7c7fdc5b6fff036e0c80f7"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Increment new_count for channel messages and members", "commit": "e1324e99bf06018a804a1a3dcc83f96cde04b1af"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Increment new_count for channel messages and members", "commit": "afbf53938bd59e1e03dfc011063b27134dd0c054"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Added new_count to notifications", "commit": "8d3d7327d777ae3150ecaa237e137f8221310dfa"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Remove unused new_count field from notification model", "commit": "4df6055566d61c769ae1759d81900d138093136e"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Increment notification count and log insertion", "commit": "dd11c8da5acc5ed97a278a705e7282edc7d50bc5"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Add logging for notification and mapper updates", "commit": "8f137cf8e6d67a8e73ee221d2f9192b7a3ab431d"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Allow channel members to be created", "commit": "30fe0bfae7f2bc9badcb16f734655e661fe976ad"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Return model directly from query", "commit": "edb35b57070a5659e8491a967880d816f2d07697"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Display username when inserting notification", "commit": "0613f6f54de9247c17b4bae924197ffb4cbd2966"} +{"repo": ".", "date": "2025-03-05", "line": "feat: Disable banned, muted, and deleted checks in channel member query", "commit": "1807cff67d3a4306f122d7df4436cc88137f299c"} +{"repo": ".", "date": "2025-03-07", "line": "feat: Implemented threads view with basic message display", "commit": "5b78165fb7e83f7e8a45f790c0f6e5a9fde758a0"} +{"repo": ".", "date": "2025-03-07", "line": "feat: Added threads view and related model updates", "commit": "a1afaacb2ea6ded2eb14eb6d8fbdaf34708d9568"} +{"repo": ".", "date": "2025-03-08", "line": "fix: Prevent race condition when reconnecting socket", "commit": "e3afc1ba6e97378688027a60d6d98cc19a519a8c"} +{"repo": ".", "date": "2025-03-08", "line": "fix: Corrected avatar styling in threads.html", "commit": "37f6725f2f7e36ec03416f191c9d16cd864991ea"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Display user avatars in threads", "commit": "095be5892db198d0a6356c8700ed0c038e419a29"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Improve thread avatar visibility", "commit": "9292e3b8f3b64084d6bcc0b13dd42d015f4799d9"} +{"repo": ".", "date": "2025-03-08", "line": "fix: Improve thread display and opacity", "commit": "24260f9c371ab2d989441e391f513f6460eaa1ec"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Unified styling for chat messages and threads", "commit": "1b72063a5b972dd726c647b7397f0ced16bd66c2"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Improved thread display and DM name handling\n\nThis update enhances thread display by adding a name and color, and fixes a bug in DM name retrieval. It also refactors the code for better readability and efficiency.", "commit": "5a72c8cd83d1374ff709556cbf4b4ddbf7a9ab7a"} +{"repo": ".", "date": "2025-03-08", "line": "fix: Improve message styling and visibility on mobile", "commit": "98c2213a862b253f8f967f428b0b248bbe3a32f7"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Improved user search display with thread-like layout and real-time updates", "commit": "0a9b66d2f76a2c4418db7149b17729bf8a2dc811"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Set default page titles and update login/register titles", "commit": "6ecd356cc08e17596ff6b5007c46def2bc17c851"} +{"repo": ".", "date": "2025-03-08", "line": "fix: Improved form submission and change event handling", "commit": "804556b74d8caa5e3a79a03cd1a8d7870843b898"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Added meta information to base.html", "commit": "7c22a70722db3fa97a813c30c01c4cd5462138eb"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Sort threads by last message timestamp", "commit": "8e195a49e3e914a4b241e95378bd9a07611715a8"} +{"repo": ".", "date": "2025-03-08", "line": "style: Improved input field styling with focus and placeholder transitions", "commit": "c9113ca09500c5b3cc277fb09b9607a505d39f30"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Added head block to base template", "commit": "62aa15a4b4d6514824378cca73084c9ce2df903b"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Implemented back button styling and layout", "commit": "ad7eab9717848584369ded6e19babb6a7b9f5b98"} +{"repo": ".", "date": "2025-03-08", "line": "Revert: Undid auto formatting", "commit": "e91ae4584aaf08cb02b89cdf26b8c43a8c8179b0"} +{"repo": ".", "date": "2025-03-08", "line": "Merge main", "commit": "dd5a9a23e8e452a03f7080656608617309bf73a5"} +{"repo": ".", "date": "2025-03-08", "line": "Merge: Tweaks for login/registration and base + image roundness", "commit": "aedfe9aa947dcd2262c825af5a4d977eb298ccb5"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Sort channels by last message time", "commit": "a219ce4d79a15ef900583ab025fb0da1df79ace3"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Filter public channels in sidebar and add private channel section", "commit": "d6061cb45b68a7b393b0e35a560d5d7bea4b9478"} +{"repo": ".", "date": "2025-03-08", "line": "fix: Sort channels by last message, handling null values", "commit": "24a504e3a7383c7a338fbe3ee09411547eed58eb"} +{"repo": ".", "date": "2025-03-08", "line": "fix: Sort channels by last message time, handling null values", "commit": "11b8f0e744fb9d6b05ce11b7475bb3f51edee96b"} +{"repo": ".", "date": "2025-03-08", "line": "feat: Implemented form preloading and autofocus on the first input element for login and register pages", "commit": "0266b2a559952d0ff767b251c2921704a6aa1abe"} +{"repo": ".", "date": "2025-03-08", "line": "fix: Corrected semicolon in loadForm call", "commit": "fd07001983fc3d3015ac7064461c14b8486155e6"} +{"repo": ".", "date": "2025-03-09", "line": "feat: Preload form and autofocus first input", "commit": "d9ac1813ba8ddad9fb602730cb2cc763aab4bc23"} +{"repo": ".", "date": "2025-03-09", "line": "fix: Sort threads by last message, handling missing timestamps", "commit": "91d8f3efd16431fe99b0e60927d1f6d9b6587f7e"} +{"repo": ".", "date": "2025-03-10", "line": "fix: Improved search user page layout", "commit": "c4e3f1fc1f10e4d98fc04e4928c62c88385fbeb8"} +{"repo": ".", "date": "2025-03-11", "line": "feat: Improve file download experience with suggested filename", "commit": "c6c2766381f75b058fb61f91556788b0720b058b"} +{"repo": ".", "date": "2025-03-13", "line": "feat: Added reply functionality and improved time display", "commit": "5cfcafe0821b3cceb753b9ddb4076a79f26a88c0"} +{"repo": ".", "date": "2025-03-13", "line": "feat: Display creation time on container", "commit": "c55927aa9c7e575544901bbee41cb9a9d3c6437a"} +{"repo": ".", "date": "2025-03-13", "line": "style: Darkened the overall theme.", "commit": "0fad298fc078e2a3ea1afee71ee92b99b83427b0"} +{"repo": ".", "date": "2025-03-13", "line": "feat: Added padding to links", "commit": "0f950218d6d783c4738966e15b2746c14043c82f"} +{"repo": ".", "date": "2025-03-13", "line": "feat: Improved reply formatting with markdown and blockquote", "commit": "d8b43dbd08afa8c4498bbc5611dd6e7d61f9b139"} +{"repo": ".", "date": "2025-03-14", "line": "feat: Increased update interval for times", "commit": "17c9731b9f8be07b247aeed29ba2ab1319e408f0"} +{"repo": ".", "date": "2025-03-15", "line": "feat: Centralized input styling in shared.css", "commit": "5b70bb9ea5cc6637ac585cf8f04efd4cde0aa621"} +{"repo": ".", "date": "2025-03-15", "line": "feat: Applied updated input styling across pages", "commit": "752f3df13a548d22646f87a87940dc64e15587f3"} +{"repo": ".", "date": "2025-03-15", "line": "feat: Refactor app modules and update script types to ES modules", "commit": "a4d79b06c49ec9f605336bf181e98455c8acd460"} +{"repo": ".", "date": "2025-03-16", "line": "feat: Convert files to modules", "commit": "a9663c8170dd2f925100eaa50e8c0019c5eee683"} +{"repo": ".", "date": "2025-03-16", "line": "feat: Refactor socket and event handling for improved reliability and structure", "commit": "4a8a614adb5e15cad18414214d30ab83464eae14"} +{"repo": ".", "date": "2025-03-16", "line": "refactor: Improved code formatting and spacing", "commit": "c9c070c497bb9af3eb5bb9915f221ec00b56b832"} +{"repo": ".", "date": "2025-03-16", "line": "fix: Reconnected socket on error", "commit": "e62a8554090009c7914b95833066ad46251da01d"} +{"repo": ".", "date": "2025-03-16", "line": "feat: Changed button colors to dark theme", "commit": "819cf8381c287e2ee88fb1d6fe789e30a1a33eff"} +{"repo": ".", "date": "2025-03-16", "line": "style: Adjusted upload button background color", "commit": "2ba28c193a826e8c1f9647f06bffb6084166c9f1"} +{"repo": ".", "date": "2025-03-16", "line": "feat: Added Umami analytics tracking", "commit": "287e10d8aa8feb2590ad48d9412ace74a9432baf"} +{"repo": ".", "date": "2025-03-17", "line": "fix: Scroll to the end of the message container", "commit": "54416ee84f88064897a824ae2c3a9e0ef2c1ccaa"} +{"repo": ".", "date": "2025-03-17", "line": "feat: Refactor header layout and logo display", "commit": "39fa8fa0cd7a725edc1f293a7e8d0ea0d5d5bca6"} +{"repo": ".", "date": "2025-03-17", "line": "feat: Add Promise.withResolvers polyfill and update templates", "commit": "7fa2817f773b47737236f4bb700023bcf8b483f1"} +{"repo": ".", "date": "2025-03-17", "line": "revert: Removed unnecessary formatting changes", "commit": "965dc930a900a5080e225bb492be2b799daed22f"} +{"repo": ".", "date": "2025-03-17", "line": "feat: Add Promise.withResolvers polyfill", "commit": "825ece4e7868be28ba03c4fde5149695b0dd9dc5"} +{"repo": ".", "date": "2025-03-18", "line": "feat: Added dump script for public channels", "commit": "3c6a0944d68ca16250ec9364d7f006b0e7eea6e8"} +{"repo": ".", "date": "2025-03-18", "line": "feat: Improved dump functionality with JSON output and user information\n\nfix: Removed unnecessary print statements from cache and dump modules", "commit": "70db15bf27c7a8fd8bf112432f02754f01bbb3d7"} +{"repo": ".", "date": "2025-03-18", "line": "feat: Refactor dump script to output to dump.txt and improve message formatting", "commit": "3960390ec45f427979ebd2b81c1a21666a47e71d"} +{"repo": ".", "date": "2025-03-20", "line": "refactor: Update session management for user registration and login", "commit": "5ba239caa8928a078362af0e6e2d1a4626bd508d"} +{"repo": ".", "date": "2025-03-22", "line": "feat: Added terminal access and updated Dockerfile and compose.yml", "commit": "7dcabde2ed0ab699ea3033f7788381e85c352b97"} +{"repo": ".", "date": "2025-03-22", "line": "feat: Added terminal access and basic shell environment.", "commit": "013d4adce57f4afec5176bbdb5e2225d529ec3b7"} +{"repo": ".", "date": "2025-03-22", "line": "feat: Added terminal page with Ubuntu option and channels sidebar", "commit": "6e68408ddfd8be2376f7453deac8f63a6bfb93e4"} +{"repo": ".", "date": "2025-03-22", "line": "feat: Add channel list and user context to template rendering", "commit": "604e27ce10dee59b1b3f6ebd359ee356b085df2a"} +{"repo": ".", "date": "2025-03-22", "line": "fix: Corrected execute permissions for r script", "commit": "c72b015073347e86f2edf1e67544b1ae31e929b0"} +{"repo": ".", "date": "2025-03-22", "line": "fix: Disable r executable and add vim and htop to apt install", "commit": "78c631e6c79b4b69b0e202a301b710c4150e6dbe"} +{"repo": ".", "date": "2025-03-22", "line": "fix: Corrected execute permissions for r script", "commit": "c8461342fd8d260da69c84f27f9e9d13b3430942"} +{"repo": ".", "date": "2025-03-23", "line": "feat: Introduce ThreadPoolExecutor for asynchronous task handling", "commit": "0bc24e8d2ef4452efc7be5286288a2531908ea55"} +{"repo": ".", "date": "2025-03-23", "line": "feat: Improve terminal display and handling", "commit": "c2d9af807a95900c4fecd7a1929c8d92393a955d"} +{"repo": ".", "date": "2025-03-23", "line": "feat: Install git during initial setup", "commit": "c5c160baae67d7e5932963f8501ed7d56dc35c21"} +{"repo": ".", "date": "2025-03-23", "line": "feat: Updated drive functionality with new views and services\n\nThis commit introduces a new drive feature with the following changes:\n\n- Added DriveModel and DriveItemModel to represent drive data.\n- Implemented DriveService and DriveItemService for managing drives and items.\n- Created DriveView for displaying drive contents as JSON.\n- Updated Application class to include drive-related setup and routes.\n- Modified Makefile to use python3.12\n- Updated src/snek/app.py to include drive view.", "commit": "7b32a7eba4d5944142a3b40616d37b0862087371"} +{"repo": ".", "date": "2025-03-23", "line": "feat: Add URL to drive items", "commit": "dec2281ac88d151afda016fc01e833dc2f0aa89e"} +{"repo": ".", "date": "2025-03-23", "line": "fix: Correctly fetch file extension from item", "commit": "1de2c55966c0ddf0fb663b935427d2c005f0fde9"} +{"repo": ".", "date": "2025-03-23", "line": "fix: Improved terminal session handling and websocket integration", "commit": "529606955a545bc25cf5899f2c79d3660bcefd54"} +{"repo": ".", "date": "2025-03-23", "line": "fix: Increased websocket thread pool size", "commit": "af4a70e8949bfde704a0499177050fdeab5300d9"} +{"repo": ".", "date": "2025-03-27", "line": "feat: Display user color in sidebar and update new message count", "commit": "5390b8bdc3dc04645258ed758f3894de00008e80"} +{"repo": ".", "date": "2025-03-27", "line": "fix: Prevent errors when user data is missing during notification updates", "commit": "877ef7970d5b7bb5f8fd0af35adad5d8d071b14d"} +{"repo": ".", "date": "2025-03-27", "line": "refactor: Remove debugging prints", "commit": "87c189b3fe897cc15ee6ac311e987bf9fe7811b9"} +{"repo": ".", "date": "2025-03-27", "line": "refactor: Cleaned up console output by removing unnecessary print statements", "commit": "71a206a19fa6b2a2ae05d886a39d2fa03f2c0f71"} +{"repo": ".", "date": "2025-03-27", "line": "feat: Added database transaction for notification creation", "commit": "bd5bb5ae65d6cb870d090382b106b705833d4cf1"} +{"repo": ".", "date": "2025-03-27", "line": "feat: Persist RPC transaction changes to the database", "commit": "13ce09a5c50ddba8956dbeb838f9dd1bcafe184a"} +{"repo": ".", "date": "2025-03-27", "line": "feat: Added task runner and task queue for asynchronous operations", "commit": "145373399dada2f4cee54af0c99e62d4a27a0f99"} +{"repo": ".", "date": "2025-03-27", "line": "refactor: Improve task management with asyncio.Queue and error handling", "commit": "e6f702a6b405f1dae0ce719177c6f7bf5b636ad8"} +{"repo": ".", "date": "2025-03-27", "line": "fix: Remove unnecessary database transaction block in RPCView", "commit": "9d5815ed1028354ac61f2d506530147a02c476e6"} +{"repo": ".", "date": "2025-03-27", "line": "feat: Added database transaction management for task execution", "commit": "8810679fd8ea2dd6e63b09bf9a4b9af5c2d0fdd2"} +{"repo": ".", "date": "2025-03-27", "line": "feat: Added task execution time measurement", "commit": "6ad3844f037735e268b42247fcc6e8605cc13f07"} +{"repo": ".", "date": "2025-03-27", "line": "feat: Asynchronously create channel messages and improve socket broadcast", "commit": "73e8779bdc42ec5f5618fad3d563544d1fad2b69"} +{"repo": ".", "date": "2025-03-27", "line": "fix: Ensure notification creation after socket broadcast", "commit": "6f043d21390d13d37772c8ae145d7a66b3919529"} +{"repo": ".", "date": "2025-03-27", "line": "fix: Await task creation in chat service", "commit": "9e50b2a6d3570c4f86fcfca6a5ce59947c0105ec"} +{"repo": ".", "date": "2025-03-28", "line": "feat: Initialized Ubuntu Dockerfile and terminal environment setup", "commit": "5fbcadad8bad7b8dd7ac3279938ff50db6b5d380"} +{"repo": ".", "date": "2025-03-29", "line": "fix: Adjusted avatar size in message template", "commit": "fe1b3d6d191176111538f1ba06018e14c44ca8f9"} +{"repo": ".", "date": "2025-03-29", "line": "```\nfeat: Added webdav support\n\nThis commit introduces webdav functionality to the application.\n\n- Added the `lxml` dependency to the `pyproject.toml` file.\n- Implemented the `WebdavApplication` class in `src/snek/webdav.py`.\n- Integrated the `WebdavApplication` as a subapp in `src/snek/app.py`.\n- Added necessary imports and code modifications to support webdav.\n```", "commit": "29139d5d0c18ad2b0ebd32db9a0b629c45c0a651"} +{"repo": ".", "date": "2025-03-29", "line": "feat: Initial implementation of WebDAV functionality with basic operations", "commit": "9a923f6bddd73df27af80ef6c8e2313816a07a48"} +{"repo": ".", "date": "2025-03-29", "line": "feat: Implemented basic WebDAV authentication", "commit": "886d21999c716ee306318796f3f159ba085f9618"} +{"repo": ".", "date": "2025-03-29", "line": "feat: Use home directory instead of drive for user folders", "commit": "6dca3a96e1cd23cc26d69aeee31ae45e14977bbd"} +{"repo": ".", "date": "2025-03-29", "line": "feat: Implemented recursive node creation for propfind requests", "commit": "3926b2d837bef181546d826b41821cf90fc755a8"} +{"repo": ".", "date": "2025-03-30", "line": "refactor: Updated home folder path to drive and removed lock implementation", "commit": "d5917b94540aee206935354f438a6a7f893278ec"} +{"repo": ".", "date": "2025-03-30", "line": "refactor: Remove unused LOCK and UNLOCK routes", "commit": "8058e4a4b0aee254edc76fa28b2c4336eb393c4b"} +{"repo": ".", "date": "2025-03-30", "line": "fix: Handle potential errors when creating user home directories", "commit": "2a47c0ba5e7344d44c3acd15d8f3efeb11722677"} +{"repo": ".", "date": "2025-04-01", "line": "feat: Added settings page with profile, notifications, and privacy sections", "commit": "32e0c959e8589f574d1c46b96d35f49c98721566"} +{"repo": ".", "date": "2025-04-01", "line": "feat: Improve responsiveness for smaller screens", "commit": "87b48af551d2ed023f77ca57d033ed0079d303f3"} +{"repo": ".", "date": "2025-04-01", "line": "refactor: Reduced message list height", "commit": "18b1ec20b67522cf816b29f3cde64a935ec5b330"} +{"repo": ".", "date": "2025-04-01", "line": "feat: Removed chat window element", "commit": "7c52c2d9d5f10623ccf479918ea5baed247a07b5"} +{"repo": ".", "date": "2025-04-01", "line": "feat: Added footer styling for improved layout", "commit": "fbd4fa4e668628c11d8b592718cfcdcca71a3c0f"} +{"repo": ".", "date": "2025-04-01", "line": "feat: Style adjustments for mobile view", "commit": "d24627b35fd2201e6baad781a11fbae0c379f366"} +{"repo": ".", "date": "2025-04-01", "line": "feat: Adjusted width for mobile responsiveness", "commit": "e3c997302b07228c0791d6307b429109b6eb3d53"} +{"repo": ".", "date": "2025-04-01", "line": "feat: Adjusted form width for mobile responsiveness", "commit": "27c0abea3147e5e00a5196fa9b351141a9d17ae2"} +{"repo": ".", "date": "2025-04-02", "line": "fix: Handle missing last message gracefully", "commit": "b365afc910522a484ad257af6df7b607712c71f2"} +{"repo": ".", "date": "2025-04-02", "line": "Error code: 422 - {'error': {'message': 'Provider returned error', 'code': 422, 'metadata': {'raw': '{\"detail\":[{\"type\":\"model_attributes_type\",\"loc\":[\"body\",\"response_format\"],\"msg\":\"Input should be a valid dictionary or object to extract fields from\",\"input\":\"json\"}]}', 'provider_name': 'DeepInfra'}}, 'user_id': 'user_2wGl2Zqx0Xrkj5eQdwZSgLEFytg'}", "commit": "99b2beeab0d55242537b7ec810bfdd69feb47103"} +{"repo": ".", "date": "2025-04-02", "line": "style: Adjusted layout and overflow properties for improved responsiveness.", "commit": "81479e7058feda9954fd74810d1294fb92e7a1c4"} +{"repo": ".", "date": "2025-04-03", "line": "feat: Refactor settings view and sidebar", "commit": "d10768403d221fbd1d50a520c083b8d1be1b3a19"} +{"repo": ".", "date": "2025-04-03", "line": "feat: Added profile settings page with nickname and description editing", "commit": "69482207461eec9c3c64ec297231989aa248dd9a"} +{"repo": ".", "date": "2025-04-06", "line": "fix: Update icons in manifest for stability on Firefox Android", "commit": "c2b8061ac292f18949d81c660c5a314cb42bcc6e"} +{"repo": ".", "date": "2025-04-07", "line": "fix: Resolved web manifest icon instability on Firefox Android", "commit": "75593fd6bb45ee6020e54f3e0de9b1ff0e6d4f5d"} +{"repo": ".", "date": "2025-04-08", "line": "feat: Added IPython dependency and improved asyncio handling", "commit": "d71d5da6bcf22d2daf5ec59832f15fe02472b95c"} +{"repo": ".", "date": "2025-04-08", "line": "feat: Introduce UserPropertyService for managing user properties", "commit": "d2e2bb811707b02f05cbf22d10ef1916b021c90d"} +{"repo": ".", "date": "2025-04-08", "line": "feat: Added debug middleware and improved routing in WebdavApplication", "commit": "d23ed3711a464f1d796ed35e58dbcaf1db6b7d84"} +{"repo": ".", "date": "2025-04-08", "line": "feat: Add debug middleware and improve WebDAV functionality\n\nThis commit introduces a debug middleware for request logging and enhances WebDAV functionality with caching and improved error handling. It also includes fixes for lock management and directory size calculations.", "commit": "13f1d2f390afdfc912d24bb63930c9ca47e05f94"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Support short and standard YouTube links in template", "commit": "b31c286a8b8442d48f9b0713a8cce41432c168d1"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Add YouTube video embedding functionality", "commit": "b0a97ad267b971f8ba298bb5a0e696810c08b026"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Implement YouTube video embedding with improved parsing", "commit": "c6575d8e525dfa2f574e1965cd3df4379cde7acd"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Simplify YouTube video embedding logic", "commit": "6138cad7827c48a86b20d4015dce818dca348f04"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Style YouTube embed to center on page", "commit": "087f9c10b44fca9f29de862562b405ef5586f151"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Add support for YouTube video embedding", "commit": "e6bd7aa15211ae0bd3be65be3a659526b1131eee"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Added video embedding functionality with improved layout", "commit": "94e94cf7ca4bdcdd581dfe074728e93412c2a621"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Implement YouTube video embedding", "commit": "6673f7b615508f0c344fe0efbebe362f5236bd84"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Implement YouTube video embedding from text", "commit": "2582df360ab0667a3d29c46b92ad4abeb397d363"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Add video embedding functionality", "commit": "656ea5f90ee56a16b0f0047cace848572dc479c7"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Simplify YouTube embedding logic", "commit": "c529fc87fd6ed7b39bf057bce44ef30d1bc17f1b"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Implement YouTube video embedding", "commit": "8fa216c06cfaf3cd249e6c44efb5e5b2735f8c6a"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Refactor asyncio and database preparation in Application class\n\nfix: Corrected indentation and removed unnecessary comments in profile form\n\nrefactor: Improve user property service for JSON handling and querying\n\nstyle: Minor formatting adjustments in template embedding\n\nfix: Corrected profile view logic for user data retrieval and form handling\n\nrefactor: Improved webdav application and file handling", "commit": "44dd77cec5639575cb86973eceb8d174d570370c"} +{"repo": ".", "date": "2025-04-09", "line": "feat: Minor formatting adjustments across modules", "commit": "743593affe276ae8ffd3751c80fe88eb4c99ac7f"} +{"repo": ".", "date": "2025-04-10", "line": "feat: Removed comments and added channel display", "commit": "0e6fbd523cd4f4279a4f230567504b30c9b3116d"} +{"repo": ".", "date": "2025-04-10", "line": "feat: Improved channel broadcasting and added user UID retrieval\n\nThis commit enhances the channel broadcasting mechanism by retrieving user UIDs directly from the channel member service. It also introduces a new method `get_user_uids` in the `ChannelMemberService` for efficient UID retrieval. Error handling has been improved in `SocketService` and `RPCView`.", "commit": "3594ac1f5984953487e0c3423c9672b01e416c28"} +{"repo": ".", "date": "2025-04-13", "line": "feat: Add stats view and cache statistics tracking", "commit": "bc65752ea252cdcd929ba0bd956455317958337a"} +{"repo": ".", "date": "2025-04-13", "line": "feat: Added stats view endpoint", "commit": "a1840cd034e7a4c792e2bcc69ff06595b1e2add3"} +{"repo": ".", "date": "2025-04-13", "line": "refactor: Moved 'r' executable and updated .bashrc for automatic updates", "commit": "22668f8a72994446ffaa109e5ae742bd61bd3bf2"} +{"repo": ".", "date": "2025-04-13", "line": "feat: Added initial .rcontext.txt file with facts and work procedure", "commit": "ec9af49f2903682cea978db15422fba4624c488d"} +{"repo": ".", "date": "2025-04-13", "line": "docs: Added a reminder to be rude but functional.", "commit": "9b49e659e575e99de717a5c64e1ba1c3c4039cb1"} +{"repo": ".", "date": "2025-04-13", "line": "refactor: Improve process handling and error management in TerminalSession", "commit": "823892a3021e674fea933b717565518dc1696031"} +{"repo": ".", "date": "2025-04-13", "line": "fix: Return empty list when search query is empty", "commit": "4a770848a6dbc558c029b083a881becf7adef8d7"} +{"repo": ".", "date": "2025-04-13", "line": "fix: Require both logged_in and uid for login_required views", "commit": "e4b0625799d9efd89e7e9518278588158b296c6c"} +{"repo": ".", "date": "2025-04-13", "line": "feat: Require login for search user view", "commit": "8ae9aac045e41fc84ebab102335a2613b3e22c08"} +{"repo": ".", "date": "2025-04-13", "line": "feat: Allow profile description editing and nickname updates\n\nThis commit introduces profile editing functionality, including a textarea for the description and an input field for the nickname. It also fixes a bug in the user property service and adds a redirect after saving changes.", "commit": "bee7d828cd67581c33946630cd22fe8edd674d15"} +{"repo": ".", "date": "2025-04-14", "line": "feat: Add user profile page and link avatars to user profiles", "commit": "3b05acffd296169eed305a55dba79d632d5f78f5"} +{"repo": ".", "date": "2025-04-14", "line": "feat: Implement user profile view and template", "commit": "a3abd854bbf0ebe2ef0ef46e7c346a995e5b6faa"} +{"repo": ".", "date": "2025-04-14", "line": "feat: Refactor user property setting logic using upsert", "commit": "9fb6e64655dff132be43e2fc867827d17ad94201"} +{"repo": ".", "date": "2025-04-14", "line": "feat: Improve user profile rendering and link avatar", "commit": "0fa04883850534fbb97755e06ebec1538dccfdc7"} +{"repo": ".", "date": "2025-04-14", "line": "fix: Use user uid instead of request user uid in user_property.set", "commit": "d4f5a4640929b1f16ccddc741f977cf7e901e7de"} +{"repo": ".", "date": "2025-04-14", "line": "feat: Added back button to user page sidebar", "commit": "3cfb79c8f560430639fceb4a278fc81dfbad2299"} +{"repo": ".", "date": "2025-04-14", "line": "fix: Removed fixed positioning from header on smaller screens", "commit": "c36ce17da5fcccfdaaf7ddf7579b0399519d078f"} +{"repo": ".", "date": "2025-04-14", "line": "feat: Added chat area class to user profile section", "commit": "4cc70640e4f7c4d65e5a0c3a503aae1f891164d5"} +{"repo": ".", "date": "2025-04-17", "line": "feat: Refactor layout and styling for user profile page", "commit": "1cd0b54656a969bcb87cbdc07687866ca43e650b"} +{"repo": ".", "date": "2025-05-06", "line": "feat: Added command-line arguments and executable script", "commit": "46a8b612b49f1094c0a8520d97d4b5642f2a57e9"} +{"repo": ".", "date": "2025-05-06", "line": "feat: Configure setuptools to find packages in src directory", "commit": "061da150f9779c6130fac0c957d4facdd59aa33a"} +{"repo": ".", "date": "2025-05-06", "line": "feat: Handle missing pty on Windows", "commit": "6312dfae47b09753675b038798cf38f00311e772"} +{"repo": ".", "date": "2025-05-06", "line": "feat: Added UUID generation and updated hashing functions", "commit": "c709ee11c99f54b58844165b6eb9993240ab0005"} +{"repo": ".", "date": "2025-05-06", "line": "feat: Added paste and file drop support for message input", "commit": "f7fda2d2c951a07bccc927a333b7feaa527556c2"} +{"repo": ".", "date": "2025-05-06", "line": "fix: Use user home folder for uploads", "commit": "529ebd23fc0b50e2606ecddc5e1199774dd18384"} +{"repo": ".", "date": "2025-05-06", "line": "Merge feat/copy-paste-drag-drop", "commit": "0f6eb5c043325e0aa0c77a587672c1d3a5dcb9fd"} +{"repo": ".", "date": "2025-05-06", "line": "feat: Implemented file paste and drag-and-drop support", "commit": "b0666a00900e1b25633433b80da1ef3dd5f2ee71"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Dispatch upload event and focus input after upload", "commit": "707788583a2387c1729950e08243d3f8f7049d7c"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Added focus functionality with Escape and G keybindings", "commit": "d6d2f2892ba3045e5555e9fb4b3d63adf51e2fc2"} +{"repo": ".", "date": "2025-05-08", "line": "fix: Prevent double focus loss during upload escape key press", "commit": "fa59dbc095b65c7b43a1b7f0e70541bd1fd0302c"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Focus input field after message display", "commit": "e153811ff34ef63892cc6aac1d5afd92cb510d14"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Focus input field after message display", "commit": "0a3e15137761d333211d8b52d178f5e150a579f1"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Focus upload while shift+G is pressed", "commit": "f6706c165e2a8ba392bde1e81d8006381fed96d3"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Focus input field after layout update during upload", "commit": "8799662159656867494b2774073b3bfb1bbe5178"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Focus input field after upload", "commit": "49ec99ef016dc754e36442d774da1d3a712bf2a7"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Updated Dockerfiles and terminal configuration\n\nThis commit updates the Dockerfiles and terminal configuration with several improvements:\n\n- Added `xterm`, `valgrind`, `ack`, `irssi`, and `lynx` to the Ubuntu Dockerfile.\n- Added Rust toolchain installation to the Ubuntu Dockerfile.\n- Modified the `r` executable placement in the Ubuntu Dockerfile.\n- Updated the terminal.html template to include fit addon and clear the screen on connection.\n- Added `$HOME/bin` to the PATH in the .bashrc file.\n- Removed the `.rcontext.txt` file.\n", "commit": "3c1d5d601fa1a9f30b2aa4fd36086102108dde94"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Updated Makefile with venv and ubuntu build target", "commit": "d0dd342e27cf4160f96faf87deff81f728e41e47"} +{"repo": ".", "date": "2025-05-08", "line": "chore: Updated Makefile and .bashrc", "commit": "31062fddbfbbf25f206060b38773a7e2c008723c"} +{"repo": ".", "date": "2025-05-08", "line": "feat: Install tmux", "commit": "3c0fea6812a5f5d759c08e6de984c0ccf5f9b9a9"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Implement user template loading based on admin UID", "commit": "02a0253c1d9c73d2918fecb8f52c4c7739c867f5"} +{"repo": ".", "date": "2025-05-09", "line": "fix: Prevent appending None to template paths", "commit": "165dda32100e52c347c7f6bb71062244b2a50ba1"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Refactor static file serving with user-specific paths", "commit": "ac570d036c26b6c7ff5abac169fdf622c10827ad"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Pass self to static_handler", "commit": "b867b6ba78574a332bf951eb6c00a6a88ded325d"} +{"repo": ".", "date": "2025-05-09", "line": "fix: Use request.session instead of self.request.session", "commit": "e359a8ebe294e0f55cf4164926011e893468e4bc"} +{"repo": ".", "date": "2025-05-09", "line": "fix: Use user static path for templates", "commit": "5b28044d9e604b2404bf4b7277a240f7fc56032c"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Refactor static file serving with `add_static`", "commit": "c56bf4fb49c986e9b653f635a81937a3ef433a5e"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Added repository management functionality\n\nThis commit introduces repository management features, including creation, updating, and deletion. It includes new models, services, and views for handling repositories, along with updated templates for a user-friendly interface. Also added uvloop for aiohttp.", "commit": "ee40c905d4448f0b39d28c4d51343b5fe111d038"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Added basic Git repository management functionality", "commit": "a5aac9a33701e3d4852fba13520771e6de82aac0"} +{"repo": ".", "date": "2025-05-09", "line": "fix: Corrected repository deletion URL and implemented repository deletion functionality", "commit": "e06776d81d0fa40e4d9d5f57a6259df8db271372"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Add uvloop dependency and fix repository path resolution", "commit": "adb59eff68e5c855fbce6f930db1ea13f59683f6"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Implement basic authentication for git receive-pack endpoint", "commit": "3ae30f1f7645203a8e8c15bd298d802fffbd2334"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Increase client max size for uploads", "commit": "e5d155e1249f9df7c504a95c98171f7e4fe5d5a4"} +{"repo": ".", "date": "2025-05-09", "line": "refactor: Migrate from argparse to click and improve application startup", "commit": "7e8ae1632d19238954ca96657da1d3950ebd413c"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Added repository view and related functionality", "commit": "95ad49df432195cb127f9fe695eac14678422b37"} +{"repo": ".", "date": "2025-05-09", "line": "Error code: 422 - {'error': {'message': 'Provider returned error', 'code': 422, 'metadata': {'raw': '{\"detail\":[{\"type\":\"model_attributes_type\",\"loc\":[\"body\",\"response_format\"],\"msg\":\"Input should be a valid dictionary or object to extract fields from\",\"input\":\"json\"}]}', 'provider_name': 'DeepInfra'}}, 'user_id': 'user_2wGl2Zqx0Xrkj5eQdwZSgLEFytg'}", "commit": "17c6124a57a394c63427a0038e598fdb40560f15"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Initial file manager UI and basic repository view", "commit": "4c34d7eda58530eddb2c8b3479627180d6eeb248"} +{"repo": ".", "date": "2025-05-09", "line": "Error code: 422 - {'error': {'message': 'Provider returned error', 'code': 422, 'metadata': {'raw': '{\"detail\":[{\"type\":\"model_attributes_type\",\"loc\":[\"body\",\"response_format\"],\"msg\":\"Input should be a valid dictionary or object to extract fields from\",\"input\":\"json\"}]}', 'provider_name': 'DeepInfra'}}, 'user_id': 'user_2wGl2Zqx0Xrkj5eQdwZSgLEFytg'}", "commit": "1616e4edb97284f705400c0598306202a083f60f"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Update dependencies and refactor repository view for improved navigation and file handling", "commit": "44ac1d2bfaa32b99d6ee51d65efdc170d846b1f8"} +{"repo": ".", "date": "2025-05-09", "line": "feat: Added DBService and RPCView db methods", "commit": "dd108c20044540c3801ac461c612392bed76ff89"} +{"repo": ".", "date": "2025-05-10", "line": "feat: Added Drive API and HTML views for file management", "commit": "f0591d493955d9c126e7dee6d1a06917c48bbbd2"} +{"repo": ".", "date": "2025-05-10", "line": "feat: Implemented typing indicator and glow effect for active users", "commit": "3412aa0bf0c0bb138c234f88fad55cb69267df79"} +{"repo": ".", "date": "2025-05-10", "line": "feat: Added channel attachment functionality with file uploads and views", "commit": "9133b7c3ce6457fa6c218b540828c752b4ba5c72"} +{"repo": ".", "date": "2025-05-10", "line": "fix: Remove debug print statement in SocketService", "commit": "4d7566de9bb3f2c54954fe72d0332caecd133ffa"} +{"repo": ".", "date": "2025-05-10", "line": "refactor: Remove hardcoded repo and user configurations", "commit": "2c9004418555dfc2a4c826e5c30aa0d59f332df7"} +{"repo": ".", "date": "2025-05-11", "line": "feat: Added SSH server functionality with user-specific home directories and authentication.", "commit": "01846bf23f7883007b99a2e100240bf3b35b30f2"} +{"repo": ".", "date": "2025-05-11", "line": "fix: Updated SSH port to 2242", "commit": "c48b84bf3ab7cff5dee5670e23db3d771e14fc46"} +{"repo": ".", "date": "2025-05-12", "line": "feat: Add image conversion and resizing support in channel attachments", "commit": "f156a153de1b2f89b99cf0490eb18bf27a611fe1"} +{"repo": ".", "date": "2025-05-13", "line": "feat: Added image conversion and resizing support for channel attachments", "commit": "ac2f68f93fd66c0d6ab3682525b2d9c94febff4d"} +{"repo": ".", "date": "2025-05-13", "line": "feat: Improve Windows compatibility and database initialization.", "commit": "a4bea9449526fc8f6b01c02d777db5c30186b830"} +{"repo": ".", "date": "2025-05-13", "line": "feat: Added WebSocket synchronization and testing scripts", "commit": "ba3152f553afcfae318811a413cdea6f5be9f413"} +{"repo": ".", "date": "2025-05-13", "line": "refactor: Moved WebSocketClient to system/websocket.py", "commit": "adad5ed4fe37038442d247d9246795a82d31093c"} +{"repo": ".", "date": "2025-05-13", "line": "fix: Remove timing output from task execution", "commit": "2e324ff11815d3c67fffa8e8d5f3e3554f154b57"} +{"repo": ".", "date": "2025-05-13", "line": "feat: Implement image click to view full size", "commit": "d09055986e9a5d971f58075a5e939a268deb26be"} +{"repo": ".", "date": "2025-05-13", "line": "fix: Add width parameter to image URLs", "commit": "8cd2f16c5c46318cb035b882197b516ae5532452"} +{"repo": ".", "date": "2025-05-13", "line": "feat: Added width attribute to image source", "commit": "015b188d5ea16a75d4ae6ef0d9bd6c2514e68fda"} +{"repo": ".", "date": "2025-05-13", "line": "fix: Corrected image width attribute in template rendering", "commit": "12d287042415554c581e7d4fcfc81bd3d733fa02"} +{"repo": ".", "date": "2025-05-13", "line": "feat: Add width parameter to image source", "commit": "964a747f42ade75dcfced5395f46727f0508172c"} +{"repo": ".", "date": "2025-05-13", "line": "feat: Add width parameter to image sources", "commit": "319c1b1b5264933a7ea1d7af6541be2a410a3328"} +{"repo": ".", "date": "2025-05-13", "line": "fix: Corrected webp image type in template", "commit": "a21e3590ef4ddad292fb914cb3454d07eb622413"} +{"repo": ".", "date": "2025-05-13", "line": "fix: Reduce image width and fix image URL in web template", "commit": "b55d74fb124b90ee24d158bc94c401b0ff19edb9"} +{"repo": ".", "date": "2025-05-13", "line": "style: Added cursor pointer to chat message images", "commit": "3858dcbd62e4032a02e9d25dffd000ade4dc7bbe"} +{"repo": ".", "date": "2025-05-13", "line": "feat: Add height parameter to image rendering", "commit": "0ea0cd96dbe536b09cb3549de47766a756f04008"} +{"repo": ".", "date": "2025-05-13", "line": "feat: Encode attachment URLs for safe transmission", "commit": "af1cf4f5aee9cc07c1c8a8e8039c19001e3d6ea9"} +{"repo": ".", "date": "2025-05-13", "line": "fix: Corrected attachment URL and added scroll to drive view", "commit": "c45b61681dc2fc2a239e1bc2672da44f7738b0c6"} +{"repo": ".", "date": "2025-05-15", "line": "feat: Introduce online user list and typing indicator", "commit": "db6d6c0106267f56822ae378a4c88385d025051a"} +{"repo": ".", "date": "2025-05-15", "line": "feat: Updated message text updating and added message age check.", "commit": "25d109beedf030523ac5d357dbbb1f9efb919edb"} +{"repo": ".", "date": "2025-05-15", "line": "feat: Implemented dialogs for online users and help, and updated templates", "commit": "dd80f3732b7f500acdd92f6e44f42f9ade0f205b"} +{"repo": ".", "date": "2025-05-15", "line": "feat: Added /live command to help dialog", "commit": "79c39828f0a3282f53e3322e19b211b5559466a1"} +{"repo": ".", "date": "2025-05-16", "line": "feat: Implement user availability service and update logic", "commit": "c5b55399a1fbea233b33a9e5fdde1fe2cd9167aa"} +{"repo": ".", "date": "2025-05-16", "line": "feat: Update socket service and attachment view\n\n- Added last_ping to user data on socket connection.\n- Fixed bug in attachment view where format was not correctly passed to image.save.\n- Improved attachment view to handle multiple attachments.", "commit": "93462d4c4b93c4f7eb81702801df9159cbb64e8e"} +{"repo": ".", "date": "2025-05-16", "line": "fix: Increased last ping cooldown to 180 seconds", "commit": "c387225a6e8aa826b944ff3c53c4045db25db758"} +{"repo": ".", "date": "2025-05-16", "line": "feat: Added cache enable/disable functionality", "commit": "00557ec9eaab7256d5c129fc8c00c12650ea3fd3"} +{"repo": ".", "date": "2025-05-17", "line": "feat: Refactor index.html with improved styling and content", "commit": "c0b4ba715c329273e4f5684d1ec2e231e5a1c7e7"} +{"repo": ".", "date": "2025-05-17", "line": "feat: Refactor chat input component with improved auto-completion and live typing functionality", "commit": "48c3daf3983e3b6e04a0c5888febceb69db9d661"} +{"repo": ".", "date": "2025-05-17", "line": "feat: Added star animation to sandbox page", "commit": "e79abf4a26454cddf766cd1ba138554817c820cb"} diff --git a/src/snek/templates/sandbox.html b/src/snek/templates/sandbox.html index 6c89c98..f77101a 100644 --- a/src/snek/templates/sandbox.html +++ b/src/snek/templates/sandbox.html @@ -5,11 +5,86 @@ import { app } from "/app.js"; const STAR_COUNT = 200; const body = document.body; +function getStarPosition(star) { + const leftPercent = parseFloat(star.style.left); + const topPercent = parseFloat(star.style.top); + + let position; + + if (topPercent < 40 && leftPercent >= 40 && leftPercent <= 60) { + position = 'North'; + } else if (topPercent > 60 && leftPercent >= 40 && leftPercent <= 60) { + position = 'South'; + } else if (leftPercent < 40 && topPercent >= 40 && topPercent <= 60) { + position = 'West'; + } else if (leftPercent > 60 && topPercent >= 40 && topPercent <= 60) { + position = 'East'; + } else if (topPercent >= 40 && topPercent <= 60 && leftPercent >= 40 && leftPercent <= 60) { + position = 'Center'; + } else { + position = 'Corner or Edge'; + } + return position +} +let stars = {} +window.stars = stars + + function createStar() { const star = document.createElement('div'); star.classList.add('star'); star.style.left = `${Math.random() * 100}%`; star.style.top = `${Math.random() * 100}%`; + star.shuffle = () => { + star.style.left = `${Math.random() * 100}%`; + star.style.top = `${Math.random() * 100}%`; + + star.position = getStarPosition(star) + } + star.position = getStarPosition(star) + +function moveStarToPosition(star, position) { + let top, left; + + switch (position) { + case 'North': + top = `${Math.random() * 20}%`; + left = `${40 + Math.random() * 20}%`; + break; + case 'South': + top = `${80 + Math.random() * 10}%`; + left = `${40 + Math.random() * 20}%`; + break; + case 'West': + top = `${40 + Math.random() * 20}%`; + left = `${Math.random() * 20}%`; + break; + case 'East': + top = `${40 + Math.random() * 20}%`; + left = `${80 + Math.random() * 10}%`; + break; + case 'Center': + top = `${45 + Math.random() * 10}%`; + left = `${45 + Math.random() * 10}%`; + break; + default: // 'Corner or Edge' fallback + top = `${Math.random() * 100}%`; + left = `${Math.random() * 100}%`; + break; + } + + star.style.top = top; + star.style.left = left; + + star.position = getStarPosition(star) +} + + + + + if(!stars[star.position]) + stars[star.position] = [] + stars[star.position].push(star) const size = Math.random() * 2 + 1; star.style.width = `${size}px`; star.style.height = `${size}px`; @@ -53,6 +128,320 @@ app.updateStarColor = updateStarColorDelayed; app.ws.addEventListener("set_typing", (data) => { updateStarColorDelayed(data.data.color); }); +window.createAvatar = () => { + let avatar = document.createElement("avatar-face") + document.querySelector("main").appendChild(avatar) + return avatar +} + + + class AvatarFace extends HTMLElement { + static get observedAttributes(){ + return ['emotion','face-color','eye-color','text','balloon-color','text-color']; + } + constructor(){ + super(); + this._shadow = this.attachShadow({mode:'open'}); + this._shadow.innerHTML = ` + <style> + :host { display:block; position:relative; } + canvas { width:100%; height:100%; display:block; } + </style> + <canvas></canvas> + `; + this._c = this._shadow.querySelector('canvas'); + this._ctx = this._c.getContext('2d'); + + // state + this._mouse = {x:0,y:0}; + this._blinkTimer = 0; + this._blinking = false; + this._lastTime = 0; + + // defaults + this._emotion = 'neutral'; + this._faceColor = '#ffdfba'; + this._eyeColor = '#000'; + this._text = ''; + this._balloonColor = '#fff'; + this._textColor = '#000'; + } + + attributeChangedCallback(name,_old,newV){ + if (name==='emotion') this._emotion = newV||'neutral'; + else if (name==='face-color') this._faceColor = newV||'#ffdfba'; + else if (name==='eye-color') this._eyeColor = newV||'#000'; + else if (name==='text') this._text = newV||''; + else if (name==='balloon-color')this._balloonColor = newV||'#fff'; + else if (name==='text-color') this._textColor = newV||'#000'; + } + + connectedCallback(){ + // watch size so canvas buffer matches display + this._ro = new ResizeObserver(entries=>{ + for(const ent of entries){ + const w = ent.contentRect.width; + const h = ent.contentRect.height; + const dpr = devicePixelRatio||1; + this._c.width = w*dpr; + this._c.height = h*dpr; + this._ctx.scale(dpr,dpr); + } + }); + this._ro.observe(this); + + // track mouse so eyes follow + this._shadow.addEventListener('mousemove', e=>{ + const r = this._c.getBoundingClientRect(); + this._mouse.x = e.clientX - r.left; + this._mouse.y = e.clientY - r.top; + }); + + this._lastTime = performance.now(); + this._raf = requestAnimationFrame(t=>this._loop(t)); + } + + disconnectedCallback(){ + cancelAnimationFrame(this._raf); + this._ro.disconnect(); + } + + _updateBlink(dt){ + this._blinkTimer -= dt; + if (this._blinkTimer<=0){ + this._blinking = !this._blinking; + this._blinkTimer = this._blinking + ? 0.1 + : 2 + Math.random()*3; + } + } + + _roundRect(x,y,w,h,r){ + const ctx = this._ctx; + ctx.beginPath(); + ctx.moveTo(x+r,y); + ctx.lineTo(x+w-r,y); + ctx.quadraticCurveTo(x+w,y, x+w,y+r); + ctx.lineTo(x+w,y+h-r); + ctx.quadraticCurveTo(x+w,y+h, x+w-r,y+h); + ctx.lineTo(x+r,y+h); + ctx.quadraticCurveTo(x,y+h, x,y+h-r); + ctx.lineTo(x,y+r); + ctx.quadraticCurveTo(x,y, x+r,y); + ctx.closePath(); + } + + _draw(ts){ + const ctx = this._ctx; + const W = this._c.clientWidth; + const H = this._c.clientHeight; + ctx.clearRect(0,0,W,H); + + // HEAD + BOB + const cx = W/2; + const cy = H/2 + Math.sin(ts*0.002)*8; + const R = Math.min(W,H)*0.25; + + // SPEECH BALLOON + if (this._text){ + const pad = 6; + ctx.font = `${R*0.15}px sans-serif`; + const m = ctx.measureText(this._text); + const tw = m.width, th = R*0.18; + const bw = tw + pad*2, bh = th + pad*2; + const bx = cx - bw/2, by = cy - R - bh - 10; + // bubble + ctx.fillStyle = this._balloonColor; + this._roundRect(bx,by,bw,bh,6); + ctx.fill(); + ctx.strokeStyle = '#888'; + ctx.lineWidth = 1.2; + ctx.stroke(); + // tail + ctx.beginPath(); + ctx.moveTo(cx-6, by+bh); + ctx.lineTo(cx+6, by+bh); + ctx.lineTo(cx, cy-R+4); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + // text + ctx.fillStyle = this._textColor; + ctx.textBaseline = 'top'; + ctx.fillText(this._text, bx+pad, by+pad); + } + + // FACE + ctx.fillStyle = this._faceColor; + ctx.beginPath(); + ctx.arc(cx,cy,R,0,2*Math.PI); + ctx.fill(); + + // EYES + const eyeY = cy - R*0.2; + const eyeX = R*0.4; + const eyeR= R*0.12; + const pupR= eyeR*0.5; + + for(let i=0;i<2;i++){ + const ex = cx + (i? eyeX:-eyeX); + const ey = eyeY; + // eyeball + ctx.fillStyle = '#fff'; + ctx.beginPath(); + ctx.arc(ex,ey,eyeR,0,2*Math.PI); + ctx.fill(); + // pupil follows + let dx = this._mouse.x - ex; + let dy = this._mouse.y - ey; + const d = Math.hypot(dx,dy); + const max = eyeR - pupR - 2; + if (d>max){ dx=dx/d*max; dy=dy/d*max; } + if (this._blinking){ + ctx.strokeStyle='#000'; + ctx.lineWidth=3; + ctx.beginPath(); + ctx.moveTo(ex-eyeR,ey); + ctx.lineTo(ex+eyeR,ey); + ctx.stroke(); + } else { + ctx.fillStyle = this._eyeColor; + ctx.beginPath(); + ctx.arc(ex+dx,ey+dy,pupR,0,2*Math.PI); + ctx.fill(); + } + } + + // ANGRY BROWS + if (this._emotion==='angry'){ + ctx.strokeStyle='#000'; + ctx.lineWidth=4; + [[-eyeX,1],[ eyeX,-1]].forEach(([off,dir])=>{ + const sx = cx+off - eyeR; + const sy = eyeY - eyeR*1.3; + const ex = cx+off + eyeR; + const ey2= sy + dir*6; + ctx.beginPath(); + ctx.moveTo(sx,sy); + ctx.lineTo(ex,ey2); + ctx.stroke(); + }); + } + + // MOUTH by emotion + const mw = R*0.6; + const my = cy + R*0.25; + ctx.strokeStyle='#a33'; + ctx.lineWidth=4; + + if (this._emotion==='surprised'){ + ctx.fillStyle='#a33'; + ctx.beginPath(); + ctx.arc(cx,my,mw*0.3,0,2*Math.PI); + ctx.fill(); + } + else if (this._emotion==='sad'){ + ctx.beginPath(); + ctx.arc(cx,my,mw/2,1.15*Math.PI,1.85*Math.PI,true); + ctx.stroke(); + } + else if (this._emotion==='angry'){ + ctx.beginPath(); + ctx.moveTo(cx-mw/2,my+2); + ctx.lineTo(cx+mw/2,my-2); + ctx.stroke(); + } + else { + const s = this._emotion==='happy'? 0.15*Math.PI:0.2*Math.PI; + const e = this._emotion==='happy'? 0.85*Math.PI:0.8*Math.PI; + ctx.beginPath(); + ctx.arc(cx,my,mw/2,s,e); + ctx.stroke(); + } + } + + _loop(ts){ + const dt = (ts - this._lastTime)/1000; + this._lastTime = ts; + this._updateBlink(dt); + this._draw(ts); + this._raf = requestAnimationFrame(t=>this._loop(t)); + } + } + customElements.define('avatar-face', AvatarFace); + + + class AvatarReplacer { + constructor(target, opts={}){ + this.target = target; + // record original inline styles so we can restore + this._oldVis = target.style.visibility || ''; + this._oldPos = target.style.position || ''; + // hide the target + target.style.visibility = 'hidden'; + // measure + const rect = target.getBoundingClientRect(); + // create avatar + this.avatar = document.createElement('avatar-face'); + // copy all supported opts into attributes + ['emotion','faceColor','eyeColor','text','balloonColor','textColor'] + .forEach(k => { + const attr = k.replace(/[A-Z]/g,m=>'-'+m.toLowerCase()); + if (opts[k] != null) this.avatar.setAttribute(attr, opts[k]); + }); + // position absolutely + const scrollX = window.pageXOffset; + const scrollY = window.pageYOffset; + Object.assign(this.avatar.style, { + position: 'absolute', + left: (rect.left + scrollX) + 'px', + top: (rect.top + scrollY) + 'px', + width: rect.width + 'px', + height: rect.height + 'px', + zIndex: 9999 + }); + document.body.appendChild(this.avatar); + } + + detach(){ + // remove avatar and restore target + if (this.avatar && this.avatar.parentNode) { + this.avatar.parentNode.removeChild(this.avatar); + this.avatar = null; + } + this.target.style.visibility = this._oldVis; + this.target.style.position = this._oldPos; + } + + // static convenience method + static attach(target, opts){ + return new AvatarReplacer(target, opts); + } + } +/* + // DEMO wiring + const btnGo = document.getElementById('go'); + const btnReset = document.getElementById('reset'); + let repl1, repl2; + + btnGo.addEventListener('click', ()=>{ + // replace #one with a happy avatar saying "Hi!" + repl1 = AvatarReplacer.attach( + document.getElementById('one'), + {emotion:'happy', text:'Hi!', balloonColor:'#fffbdd', textColor:'#333'} + ); + // replace #two with a surprised avatar + repl2 = AvatarReplacer.attach( + document.getElementById('two'), + {emotion:'surprised', faceColor:'#eeffcc', text:'Wow!', balloonColor:'#ddffdd'} + ); + }); + + btnReset.addEventListener('click', ()=>{ + if (repl1) repl1.detach(); + if (repl2) repl2.detach(); + }); +*/ /* class StarField { diff --git a/src/snek/view/avatar.py b/src/snek/view/avatar.py index a85b876..ad81301 100644 --- a/src/snek/view/avatar.py +++ b/src/snek/view/avatar.py @@ -29,12 +29,35 @@ from aiohttp import web from multiavatar import multiavatar from snek.system.view import BaseView +from snek.view.avatar_animal import generate_avatar_with_options +import functools class AvatarView(BaseView): login_required = False + + def __init__(self, *args,**kwargs): + super().__init__(*args,**kwargs) + self.avatars = {} async def get(self): + uid = self.request.match_info.get("uid") + while True: + try: + return web.Response(text=self._get(uid), content_type="image/svg+xml") + except Exception as e: + pass + + + def _get(self, uid): + if uid in self.avatars: + return self.avatars[uid] + + avatar = generate_avatar_with_options(self.request.query) + self.avatars[uid] = avatar + return avatar + + async def get2(self): uid = self.request.match_info.get("uid") if uid == "unique": uid = str(uuid.uuid4()) diff --git a/src/snek/view/avatar_animal.py b/src/snek/view/avatar_animal.py new file mode 100644 index 0000000..3a7f653 --- /dev/null +++ b/src/snek/view/avatar_animal.py @@ -0,0 +1,871 @@ +import random +import math +import argparse +import json +from typing import Dict, List, Tuple, Optional, Union + +class AnimalAvatarGenerator: + """A generator for animal-themed avatar SVGs.""" + + # Constants + ANIMALS = [ + "cat", "dog", "fox", "wolf", "bear", "panda", "koala", "tiger", "lion", + "rabbit", "monkey", "elephant", "giraffe", "zebra", "penguin", "owl", + "deer", "raccoon", "squirrel", "hedgehog", "otter", "frog" + ] + + COLOR_PALETTES = { + "natural": { + "cat": ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#000000", "#808080", "#FFFFFF", "#FFA500"], + "dog": ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#000000", "#808080", "#FFFFFF", "#FFA500"], + "fox": ["#FF6600", "#FF7F00", "#FF8C00", "#FFA500", "#FFFFFF", "#000000"], + "wolf": ["#808080", "#A9A9A9", "#D3D3D3", "#FFFFFF", "#000000", "#696969"], + "bear": ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#000000", "#FFFFFF"], + "panda": ["#000000", "#FFFFFF"], + "koala": ["#808080", "#A9A9A9", "#D3D3D3", "#FFFFFF", "#000000"], + "tiger": ["#FF8C00", "#FF7F00", "#FFFFFF", "#000000"], + "lion": ["#DAA520", "#B8860B", "#CD853F", "#D2B48C", "#FFFFFF", "#000000"], + "rabbit": ["#FFFFFF", "#F5F5F5", "#D3D3D3", "#A9A9A9", "#FFC0CB", "#000000"], + "monkey": ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#000000"], + "elephant": ["#808080", "#A9A9A9", "#D3D3D3", "#FFFFFF", "#000000"], + "giraffe": ["#DAA520", "#B8860B", "#F5DEB3", "#FFFFFF", "#000000"], + "zebra": ["#000000", "#FFFFFF"], + "penguin": ["#000000", "#FFFFFF", "#FFA500"], + "owl": ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#000000", "#FFFFFF", "#FFC0CB"], + "deer": ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#FFFFFF", "#000000"], + "raccoon": ["#808080", "#A9A9A9", "#D3D3D3", "#FFFFFF", "#000000"], + "squirrel": ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#000000"], + "hedgehog": ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#000000"], + "otter": ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#000000"], + "frog": ["#008000", "#00FF00", "#ADFF2F", "#7FFF00", "#000000", "#FFFFFF"] + }, + "pastel": { + "all": ["#FFB6C1", "#FFD700", "#FFDAB9", "#98FB98", "#ADD8E6", "#DDA0DD", "#F0E68C", "#FFFFE0"] + }, + "vibrant": { + "all": ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#FFA500", "#FF1493"] + }, + "mono": { + "all": ["#000000", "#333333", "#666666", "#999999", "#CCCCCC", "#FFFFFF"] + } + } + + EYE_STYLES = ["round", "oval", "almond", "wide", "narrow", "cute"] + + FACE_SHAPES = ["round", "oval", "square", "heart", "triangular", "diamond"] + + EAR_STYLES = { + "cat": ["pointed", "folded", "round", "large", "small"], + "dog": ["floppy", "pointed", "round", "large", "small"], + "fox": ["pointed", "large", "small"], + "wolf": ["pointed", "large", "small"], + "bear": ["round", "small"], + "panda": ["round", "small"], + "koala": ["round", "large"], + "tiger": ["round", "small"], + "lion": ["round", "small"], + "rabbit": ["long", "floppy", "standing"], + "monkey": ["round", "small"], + "elephant": ["large", "wide"], + "giraffe": ["small", "pointed"], + "zebra": ["pointed", "small"], + "penguin": ["none"], + "owl": ["none", "tufted"], + "deer": ["small", "pointed"], + "raccoon": ["round", "small"], + "squirrel": ["pointed", "small"], + "hedgehog": ["round", "small"], + "otter": ["round", "small"], + "frog": ["none"] + } + + NOSE_STYLES = ["round", "triangular", "small", "large", "heart", "button"] + + SPECIAL_FEATURES = { + "cat": ["whiskers", "stripes", "spots"], + "dog": ["spots", "patch", "whiskers"], + "fox": ["mask", "whiskers", "brush_tail"], + "wolf": ["mask", "whiskers", "brush_tail"], + "bear": ["none", "patch"], + "panda": ["eye_patches", "none"], + "koala": ["none", "nose_patch"], + "tiger": ["stripes", "none"], + "lion": ["mane", "none"], + "rabbit": ["whiskers", "nose_patch"], + "monkey": ["none", "cheek_patches"], + "elephant": ["tusks", "none"], + "giraffe": ["spots", "none"], + "zebra": ["stripes", "none"], + "penguin": ["bib", "none"], + "owl": ["feather_tufts", "none"], + "deer": ["antlers", "spots", "none"], + "raccoon": ["mask", "whiskers", "none"], + "squirrel": ["bushy_tail", "none"], + "hedgehog": ["spikes", "none"], + "otter": ["whiskers", "none"], + "frog": ["spots", "none"] + } + + EXPRESSIONS = ["happy", "serious", "surprised", "sleepy", "wink"] + + def __init__(self, seed: Optional[int] = None): + """Initialize the avatar generator with an optional seed for reproducibility.""" + if seed is not None: + random.seed(seed) + + def _get_colors(self, animal: str, color_palette: str) -> List[str]: + """Get colors for the given animal and palette.""" + if color_palette in self.COLOR_PALETTES: + if animal in self.COLOR_PALETTES[color_palette]: + return self.COLOR_PALETTES[color_palette][animal] + elif "all" in self.COLOR_PALETTES[color_palette]: + return self.COLOR_PALETTES[color_palette]["all"] + + # Default to natural palette for the animal or general natural colors + if animal in self.COLOR_PALETTES["natural"]: + return self.COLOR_PALETTES["natural"][animal] + + # If no specific colors found, use a mix of browns and grays + return ["#8B4513", "#A0522D", "#D2B48C", "#F5DEB3", "#808080", "#A9A9A9", "#FFFFFF"] + + def _get_ear_style(self, animal: str) -> str: + """Get a random ear style appropriate for the animal.""" + if animal in self.EAR_STYLES: + return random.choice(self.EAR_STYLES[animal]) + return "none" # Default for animals not in the list + + def _get_special_feature(self, animal: str) -> str: + """Get a random special feature appropriate for the animal.""" + if animal in self.SPECIAL_FEATURES: + return random.choice(self.SPECIAL_FEATURES[animal]) + return "none" # Default for animals not in the list + + def _draw_circle(self, cx: float, cy: float, r: float, fill: str, + stroke: str = "none", stroke_width: float = 1.0) -> str: + """Generate SVG for a circle.""" + return f'<circle cx="{cx}" cy="{cy}" r="{r}" fill="{fill}" stroke="{stroke}" stroke-width="{stroke_width}" />' + + def _draw_ellipse(self, cx: float, cy: float, rx: float, ry: float, + fill: str, stroke: str = "none", stroke_width: float = 1.0) -> str: + """Generate SVG for an ellipse.""" + return f'<ellipse cx="{cx}" cy="{cy}" rx="{rx}" ry="{ry}" fill="{fill}" stroke="{stroke}" stroke-width="{stroke_width}" />' + + def _draw_path(self, d: str, fill: str, stroke: str = "none", + stroke_width: float = 1.0, stroke_linecap: str = "round") -> str: + """Generate SVG for a path.""" + return f'<path d="{d}" fill="{fill}" stroke="{stroke}" stroke-width="{stroke_width}" stroke-linecap="{stroke_linecap}" />' + + def _draw_polygon(self, points: str, fill: str, stroke: str = "none", + stroke_width: float = 1.0) -> str: + """Generate SVG for a polygon.""" + return f'<polygon points="{points}" fill="{fill}" stroke="{stroke}" stroke-width="{stroke_width}" />' + + def _draw_face(self, animal: str, face_shape: str, face_color: str, + x: float, y: float, size: float) -> str: + """Draw the animal's face based on face shape.""" + elements = [] + + if face_shape == "round": + elements.append(self._draw_circle(x, y, size * 0.4, face_color)) + elif face_shape == "oval": + elements.append(self._draw_ellipse(x, y, size * 0.35, size * 0.45, face_color)) + elif face_shape == "square": + points = (f"{x-size*0.35},{y-size*0.35} {x+size*0.35},{y-size*0.35} " + f"{x+size*0.35},{y+size*0.35} {x-size*0.35},{y+size*0.35}") + elements.append(self._draw_polygon(points, face_color)) + elif face_shape == "heart": + # Create a heart shape using paths + cx, cy = x, y + size * 0.05 + r = size * 0.2 + path = (f"M {cx} {cy-r*0.4} " + f"C {cx-r*1.5} {cy-r*1.5}, {cx-r*2} {cy+r*0.5}, {cx} {cy+r} " + f"C {cx+r*2} {cy+r*0.5}, {cx+r*1.5} {cy-r*1.5}, {cx} {cy-r*0.4} Z") + elements.append(self._draw_path(path, face_color)) + elif face_shape == "triangular": + points = f"{x},{y-size*0.4} {x+size*0.4},{y+size*0.3} {x-size*0.4},{y+size*0.3}" + elements.append(self._draw_polygon(points, face_color)) + elif face_shape == "diamond": + points = f"{x},{y-size*0.4} {x+size*0.35},{y} {x},{y+size*0.4} {x-size*0.35},{y}" + elements.append(self._draw_polygon(points, face_color)) + + return "\n".join(elements) + + def _draw_ears(self, animal: str, ear_style: str, face_color: str, + inner_color: str, x: float, y: float, size: float) -> str: + """Draw the animal's ears based on ear style.""" + elements = [] + + if ear_style == "none": + return "" + + if ear_style == "pointed": + # Left ear + points_left = f"{x-size*0.2},{y-size*0.1} {x-size*0.35},{y-size*0.45} {x-size*0.05},{y-size*0.15}" + elements.append(self._draw_polygon(points_left, face_color)) + + # Right ear + points_right = f"{x+size*0.2},{y-size*0.1} {x+size*0.35},{y-size*0.45} {x+size*0.05},{y-size*0.15}" + elements.append(self._draw_polygon(points_right, face_color)) + + # Inner ears + points_inner_left = f"{x-size*0.2},{y-size*0.13} {x-size*0.3},{y-size*0.38} {x-size*0.1},{y-size*0.17}" + elements.append(self._draw_polygon(points_inner_left, inner_color)) + + points_inner_right = f"{x+size*0.2},{y-size*0.13} {x+size*0.3},{y-size*0.38} {x+size*0.1},{y-size*0.17}" + elements.append(self._draw_polygon(points_inner_right, inner_color)) + + elif ear_style == "round": + # Left ear + elements.append(self._draw_circle(x-size*0.25, y-size*0.25, size*0.15, face_color)) + + # Right ear + elements.append(self._draw_circle(x+size*0.25, y-size*0.25, size*0.15, face_color)) + + # Inner ears + elements.append(self._draw_circle(x-size*0.25, y-size*0.25, size*0.08, inner_color)) + elements.append(self._draw_circle(x+size*0.25, y-size*0.25, size*0.08, inner_color)) + + elif ear_style == "folded" or ear_style == "floppy": + # Left ear + path_left = (f"M {x-size*0.15} {y-size*0.15} " + f"C {x-size*0.3} {y-size*0.4}, {x-size*0.4} {y-size*0.2}, {x-size*0.35} {y}") + elements.append(self._draw_path(path_left, face_color, stroke="none", stroke_width=size*0.08)) + + # Right ear + path_right = (f"M {x+size*0.15} {y-size*0.15} " + f"C {x+size*0.3} {y-size*0.4}, {x+size*0.4} {y-size*0.2}, {x+size*0.35} {y}") + elements.append(self._draw_path(path_right, face_color, stroke="none", stroke_width=size*0.08)) + + elif ear_style == "long" or ear_style == "standing": + # Left ear + points_left = f"{x-size*0.2},{y-size*0.15} {x-size*0.3},{y-size*0.6} {x-size*0.05},{y-size*0.15}" + elements.append(self._draw_polygon(points_left, face_color)) + + # Right ear + points_right = f"{x+size*0.2},{y-size*0.15} {x+size*0.3},{y-size*0.6} {x+size*0.05},{y-size*0.15}" + elements.append(self._draw_polygon(points_right, face_color)) + + # Inner ears + points_inner_left = f"{x-size*0.18},{y-size*0.18} {x-size*0.25},{y-size*0.5} {x-size*0.1},{y-size*0.18}" + elements.append(self._draw_polygon(points_inner_left, inner_color)) + + points_inner_right = f"{x+size*0.18},{y-size*0.18} {x+size*0.25},{y-size*0.5} {x+size*0.1},{y-size*0.18}" + elements.append(self._draw_polygon(points_inner_right, inner_color)) + + elif ear_style == "large": + # Left ear + elements.append(self._draw_ellipse(x-size*0.25, y-size*0.3, size*0.2, size*0.25, face_color)) + + # Right ear + elements.append(self._draw_ellipse(x+size*0.25, y-size*0.3, size*0.2, size*0.25, face_color)) + + # Inner ears + elements.append(self._draw_ellipse(x-size*0.25, y-size*0.3, size*0.1, size*0.15, inner_color)) + elements.append(self._draw_ellipse(x+size*0.25, y-size*0.3, size*0.1, size*0.15, inner_color)) + + elif ear_style == "small": + # Left ear + elements.append(self._draw_ellipse(x-size*0.22, y-size*0.22, size*0.1, size*0.12, face_color)) + + # Right ear + elements.append(self._draw_ellipse(x+size*0.22, y-size*0.22, size*0.1, size*0.12, face_color)) + + # Inner ears + elements.append(self._draw_ellipse(x-size*0.22, y-size*0.22, size*0.05, size*0.06, inner_color)) + elements.append(self._draw_ellipse(x+size*0.22, y-size*0.22, size*0.05, size*0.06, inner_color)) + + elif ear_style == "tufted" and animal == "owl": + # Left ear tuft + points_left = f"{x-size*0.2},{y-size*0.15} {x-size*0.35},{y-size*0.45} {x-size*0.05},{y-size*0.15}" + elements.append(self._draw_polygon(points_left, face_color)) + + # Right ear tuft + points_right = f"{x+size*0.2},{y-size*0.15} {x+size*0.35},{y-size*0.45} {x+size*0.05},{y-size*0.15}" + elements.append(self._draw_polygon(points_right, face_color)) + + elif ear_style == "wide" and animal == "elephant": + # Left ear + path_left = (f"M {x-size*0.15} {y-size*0.1} " + f"C {x-size*0.5} {y-size*0.2}, {x-size*0.6} {y+size*0.2}, {x-size*0.15} {y+size*0.2}") + elements.append(self._draw_path(path_left, face_color, stroke=face_color, stroke_width=size*0.04)) + + # Right ear + path_right = (f"M {x+size*0.15} {y-size*0.1} " + f"C {x+size*0.5} {y-size*0.2}, {x+size*0.6} {y+size*0.2}, {x+size*0.15} {y+size*0.2}") + elements.append(self._draw_path(path_right, face_color, stroke=face_color, stroke_width=size*0.04)) + + return "\n".join(elements) + + def _draw_eyes(self, eye_style: str, expression: str, eye_color: str, + x: float, y: float, size: float) -> str: + """Draw the animal's eyes based on eye style and expression.""" + elements = [] + eye_spacing = size * 0.2 + + if eye_style == "round": + eye_size = size * 0.08 + # Left eye + elements.append(self._draw_circle(x - eye_spacing, y - size * 0.05, eye_size, "#FFFFFF")) + elements.append(self._draw_circle(x - eye_spacing, y - size * 0.05, eye_size * 0.6, eye_color)) + + # Right eye + elements.append(self._draw_circle(x + eye_spacing, y - size * 0.05, eye_size, "#FFFFFF")) + elements.append(self._draw_circle(x + eye_spacing, y - size * 0.05, eye_size * 0.6, eye_color)) + + elif eye_style == "oval": + eye_width = size * 0.1 + eye_height = size * 0.07 + # Left eye + elements.append(self._draw_ellipse(x - eye_spacing, y - size * 0.05, eye_width, eye_height, "#FFFFFF")) + elements.append(self._draw_ellipse(x - eye_spacing, y - size * 0.05, eye_width * 0.6, eye_height * 0.6, eye_color)) + + # Right eye + elements.append(self._draw_ellipse(x + eye_spacing, y - size * 0.05, eye_width, eye_height, "#FFFFFF")) + elements.append(self._draw_ellipse(x + eye_spacing, y - size * 0.05, eye_width * 0.6, eye_height * 0.6, eye_color)) + + elif eye_style == "almond": + # Left eye - almond shape + path_left = (f"M {x-eye_spacing-size*0.1} {y-size*0.05} " + f"C {x-eye_spacing} {y-size*0.12}, {x-eye_spacing} {y+size*0.02}, {x-eye_spacing+size*0.1} {y-size*0.05} " + f"C {x-eye_spacing} {y+size*0.02}, {x-eye_spacing} {y-size*0.12}, {x-eye_spacing-size*0.1} {y-size*0.05} Z") + elements.append(self._draw_path(path_left, "#FFFFFF")) + elements.append(self._draw_circle(x - eye_spacing, y - size * 0.05, size * 0.05, eye_color)) + + # Right eye - almond shape + path_right = (f"M {x+eye_spacing-size*0.1} {y-size*0.05} " + f"C {x+eye_spacing} {y-size*0.12}, {x+eye_spacing} {y+size*0.02}, {x+eye_spacing+size*0.1} {y-size*0.05} " + f"C {x+eye_spacing} {y+size*0.02}, {x+eye_spacing} {y-size*0.12}, {x+eye_spacing-size*0.1} {y-size*0.05} Z") + elements.append(self._draw_path(path_right, "#FFFFFF")) + elements.append(self._draw_circle(x + eye_spacing, y - size * 0.05, size * 0.05, eye_color)) + + elif eye_style == "wide": + eye_width = size * 0.12 + eye_height = size * 0.08 + # Left eye + elements.append(self._draw_ellipse(x - eye_spacing, y - size * 0.05, eye_width, eye_height, "#FFFFFF")) + elements.append(self._draw_ellipse(x - eye_spacing, y - size * 0.05, eye_width * 0.5, eye_height * 0.6, eye_color)) + + # Right eye + elements.append(self._draw_ellipse(x + eye_spacing, y - size * 0.05, eye_width, eye_height, "#FFFFFF")) + elements.append(self._draw_ellipse(x + eye_spacing, y - size * 0.05, eye_width * 0.5, eye_height * 0.6, eye_color)) + + elif eye_style == "narrow": + eye_width = size * 0.12 + eye_height = size * 0.04 + # Left eye + elements.append(self._draw_ellipse(x - eye_spacing, y - size * 0.05, eye_width, eye_height, "#FFFFFF")) + elements.append(self._draw_ellipse(x - eye_spacing, y - size * 0.05, eye_width * 0.4, eye_height * 0.7, eye_color)) + + # Right eye + elements.append(self._draw_ellipse(x + eye_spacing, y - size * 0.05, eye_width, eye_height, "#FFFFFF")) + elements.append(self._draw_ellipse(x + eye_spacing, y - size * 0.05, eye_width * 0.4, eye_height * 0.7, eye_color)) + + elif eye_style == "cute": + eye_size = size * 0.1 + # Left eye + elements.append(self._draw_circle(x - eye_spacing, y - size * 0.05, eye_size, "#FFFFFF")) + elements.append(self._draw_circle(x - eye_spacing - size * 0.02, y - size * 0.07, eye_size * 0.5, eye_color)) + elements.append(self._draw_circle(x - eye_spacing - size * 0.03, y - size * 0.08, eye_size * 0.15, "#FFFFFF")) + + # Right eye + elements.append(self._draw_circle(x + eye_spacing, y - size * 0.05, eye_size, "#FFFFFF")) + elements.append(self._draw_circle(x + eye_spacing - size * 0.02, y - size * 0.07, eye_size * 0.5, eye_color)) + elements.append(self._draw_circle(x + eye_spacing - size * 0.03, y - size * 0.08, eye_size * 0.15, "#FFFFFF")) + + # Apply expression + if expression == "happy": + # Close bottom half of eyes slightly + path_left = (f"M {x-eye_spacing-size*0.1} {y-size*0.03} " + f"Q {x-eye_spacing} {y+size*0.02}, {x-eye_spacing+size*0.1} {y-size*0.03}") + elements.append(self._draw_path(path_left, face_color, stroke="#000000", stroke_width=size*0.01)) + + path_right = (f"M {x+eye_spacing-size*0.1} {y-size*0.03} " + f"Q {x+eye_spacing} {y+size*0.02}, {x+eye_spacing+size*0.1} {y-size*0.03}") + elements.append(self._draw_path(path_right, face_color, stroke="#000000", stroke_width=size*0.01)) + + elif expression == "serious": + # Serious eyebrows + path_left = (f"M {x-eye_spacing-size*0.08} {y-size*0.12} " + f"L {x-eye_spacing+size*0.08} {y-size*0.15}") + elements.append(self._draw_path(path_left, "none", stroke="#000000", stroke_width=size*0.02)) + + path_right = (f"M {x+eye_spacing-size*0.08} {y-size*0.15} " + f"L {x+eye_spacing+size*0.08} {y-size*0.12}") + elements.append(self._draw_path(path_right, "none", stroke="#000000", stroke_width=size*0.02)) + + elif expression == "surprised": + # Raise eyebrows + path_left = (f"M {x-eye_spacing-size*0.08} {y-size*0.15} " + f"Q {x-eye_spacing} {y-size*0.18}, {x-eye_spacing+size*0.08} {y-size*0.15}") + elements.append(self._draw_path(path_left, "none", stroke="#000000", stroke_width=size*0.015)) + + path_right = (f"M {x+eye_spacing-size*0.08} {y-size*0.15} " + f"Q {x+eye_spacing} {y-size*0.18}, {x+eye_spacing+size*0.08} {y-size*0.15}") + elements.append(self._draw_path(path_right, "none", stroke="#000000", stroke_width=size*0.015)) + + elif expression == "sleepy": + # Half-closed eyes + path_left = (f"M {x-eye_spacing-size*0.1} {y-size*0.08} " + f"Q {x-eye_spacing} {y-size*0.01}, {x-eye_spacing+size*0.1} {y-size*0.08}") + elements.append(self._draw_path(path_left, "none", stroke="#000000", stroke_width=size*0.015)) + + path_right = (f"M {x+eye_spacing-size*0.1} {y-size*0.08} " + f"Q {x+eye_spacing} {y-size*0.01}, {x+eye_spacing+size*0.1} {y-size*0.08}") + elements.append(self._draw_path(path_right, "none", stroke="#000000", stroke_width=size*0.015)) + + elif expression == "wink": + # Right eye normal + # Left eye winking + path_left = (f"M {x-eye_spacing-size*0.1} {y-size*0.05} " + f"Q {x-eye_spacing} {y-size*0.1}, {x-eye_spacing+size*0.1} {y-size*0.05}") + elements.append(self._draw_path(path_left, "none", stroke="#000000", stroke_width=size*0.02)) + + return "\n".join(elements) + + def _draw_nose(self, animal: str, nose_style: str, nose_color: str, + x: float, y: float, size: float) -> str: + """Draw the animal's nose based on nose style.""" + elements = [] + + if nose_style == "round": + elements.append(self._draw_circle(x, y + size * 0.05, size * 0.08, nose_color)) + + elif nose_style == "triangular": + points = f"{x},{y+size*0.02} {x-size*0.08},{y+size*0.12} {x+size*0.08},{y+size*0.12}" + elements.append(self._draw_polygon(points, nose_color)) + + elif nose_style == "small": + elements.append(self._draw_circle(x, y + size * 0.05, size * 0.05, nose_color)) + + elif nose_style == "large": + if animal in ["dog", "bear", "panda", "koala"]: + elements.append(self._draw_ellipse(x, y + size * 0.05, size * 0.1, size * 0.08, nose_color)) + else: + elements.append(self._draw_circle(x, y + size * 0.05, size * 0.1, nose_color)) + + elif nose_style == "heart": + # Heart-shaped nose + cx, cy = x, y + size * 0.05 + r = size * 0.06 + path = (f"M {cx} {cy-r*0.2} " + f"C {cx-r*1.5} {cy-r*1.2}, {cx-r*1.8} {cy+r*0.6}, {cx} {cy+r*0.8} " + f"C {cx+r*1.8} {cy+r*0.6}, {cx+r*1.5} {cy-r*1.2}, {cx} {cy-r*0.2} Z") + elements.append(self._draw_path(path, nose_color)) + + elif nose_style == "button": + elements.append(self._draw_circle(x, y + size * 0.05, size * 0.06, nose_color)) + elements.append(self._draw_circle(x, y + size * 0.05, size * 0.04, nose_color, stroke="#000000", stroke_width=size*0.01)) + + return "\n".join(elements) + + def _draw_mouth(self, expression: str, x: float, y: float, size: float) -> str: + """Draw the animal's mouth based on expression.""" + elements = [] + + if expression == "happy": + path = (f"M {x-size*0.15} {y+size*0.12} " + f"Q {x} {y+size*0.25}, {x+size*0.15} {y+size*0.12}") + elements.append(self._draw_path(path, "none", stroke="#000000", stroke_width=size*0.02)) + + elif expression == "serious": + path = (f"M {x-size*0.12} {y+size*0.15} " + f"L {x+size*0.12} {y+size*0.15}") + elements.append(self._draw_path(path, "none", stroke="#000000", stroke_width=size*0.02)) + + elif expression == "surprised": + elements.append(self._draw_ellipse(x, y + size * 0.15, size * 0.06, size * 0.08, "#FFFFFF", + stroke="#000000", stroke_width=size*0.01)) + + elif expression == "sleepy": + path = (f"M {x-size*0.08} {y+size*0.15} " + f"Q {x} {y+size*0.12}, {x+size*0.08} {y+size*0.15}") + elements.append(self._draw_path(path, "none", stroke="#000000", stroke_width=size*0.015)) + + elif expression == "wink": + path = (f"M {x-size*0.15} {y+size*0.12} " + f"Q {x} {y+size*0.22}, {x+size*0.15} {y+size*0.12}") + elements.append(self._draw_path(path, "none", stroke="#000000", stroke_width=size*0.02)) + + return "\n".join(elements) + + def _draw_special_features(self, animal: str, special_feature: str, face_color: str, + accent_color: str, x: float, y: float, size: float) -> str: + """Draw special features based on animal and feature type.""" + elements = [] + + if special_feature == "none": + return "" + + elif special_feature == "whiskers": + # Left whiskers + for i in range(3): + angle = -30 + i * 30 + length = size * 0.25 + end_x = x - size * 0.15 + length * math.cos(math.radians(angle)) + end_y = y + size * 0.05 + length * math.sin(math.radians(angle)) + elements.append(self._draw_path( + f"M {x-size*0.15} {y+size*0.05} L {end_x} {end_y}", + "none", stroke="#000000", stroke_width=size*0.01)) + + # Right whiskers + for i in range(3): + angle = -150 + i * 30 + length = size * 0.25 + end_x = x + size * 0.15 + length * math.cos(math.radians(angle)) + end_y = y + size * 0.05 + length * math.sin(math.radians(angle)) + elements.append(self._draw_path( + f"M {x+size*0.15} {y+size*0.05} L {end_x} {end_y}", + "none", stroke="#000000", stroke_width=size*0.01)) + + elif special_feature == "stripes": + # Vertical stripes + if animal == "tiger": + for i in range(3): + offset = -size * 0.2 + i * size * 0.2 + path = (f"M {x+offset} {y-size*0.3} " + f"Q {x+offset+size*0.1} {y}, {x+offset} {y+size*0.3}") + elements.append(self._draw_path(path, "none", stroke=accent_color, stroke_width=size*0.04)) + # Horizontal stripes for zebra + elif animal == "zebra": + for i in range(3): + offset = -size * 0.2 + i * size * 0.2 + path = (f"M {x-size*0.3} {y+offset} " + f"Q {x} {y+offset+size*0.1}, {x+size*0.3} {y+offset}") + elements.append(self._draw_path(path, "none", stroke=accent_color, stroke_width=size*0.04)) + + elif special_feature == "spots": + # Random spots + num_spots = random.randint(3, 6) + for _ in range(num_spots): + spot_x = x + random.uniform(-size * 0.3, size * 0.3) + spot_y = y + random.uniform(-size * 0.3, size * 0.3) + spot_size = random.uniform(size * 0.03, size * 0.08) + elements.append(self._draw_circle(spot_x, spot_y, spot_size, accent_color)) + + elif special_feature == "patch": + # Eye patch or face patch + if animal == "dog": + elements.append(self._draw_ellipse(x - size * 0.2, y, size * 0.2, size * 0.25, accent_color)) + else: + # Generic face patch + elements.append(self._draw_ellipse(x, y + size * 0.2, size * 0.2, size * 0.15, accent_color)) + + elif special_feature == "mask": + if animal == "raccoon": + # Raccoon mask + path = (f"M {x-size*0.3} {y-size*0.1} " + f"Q {x} {y-size*0.3}, {x+size*0.3} {y-size*0.1} " + f"Q {x+size*0.2} {y+size*0.1}, {x} {y+size*0.15} " + f"Q {x-size*0.2} {y+size*0.1}, {x-size*0.3} {y-size*0.1} Z") + elements.append(self._draw_path(path, accent_color)) + elif animal in ["fox", "wolf"]: + # Fox/wolf mask + path = (f"M {x-size*0.3} {y-size*0.1} " + f"L {x} {y+size*0.1} " + f"L {x+size*0.3} {y-size*0.1} " + f"Q {x+size*0.15} {y-size*0.05}, {x} {y-size*0.1} " + f"Q {x-size*0.15} {y-size*0.05}, {x-size*0.3} {y-size*0.1} Z") + elements.append(self._draw_path(path, accent_color)) + + elif special_feature == "eye_patches" and animal == "panda": + # Panda eye patches + elements.append(self._draw_ellipse(x - size * 0.2, y - size * 0.05, size * 0.15, size * 0.2, accent_color)) + elements.append(self._draw_ellipse(x + size * 0.2, y - size * 0.05, size * 0.15, size * 0.2, accent_color)) + + elif special_feature == "nose_patch": + elements.append(self._draw_ellipse(x, y + size * 0.05, size * 0.12, size * 0.1, accent_color)) + + elif special_feature == "mane" and animal == "lion": + # Lion mane + for i in range(12): + angle = i * 30 + outer_x = x + size * 0.5 * math.cos(math.radians(angle)) + outer_y = y + size * 0.5 * math.sin(math.radians(angle)) + inner_x = x + size * 0.3 * math.cos(math.radians(angle)) + inner_y = y + size * 0.3 * math.sin(math.radians(angle)) + + # Draw mane sections + path = (f"M {inner_x} {inner_y} " + f"L {outer_x} {outer_y} " + f"A {size*0.5} {size*0.5} 0 0 1 " + f"{x + size * 0.5 * math.cos(math.radians(angle + 30))} " + f"{y + size * 0.5 * math.sin(math.radians(angle + 30))} " + f"L {x + size * 0.3 * math.cos(math.radians(angle + 30))} " + f"{y + size * 0.3 * math.sin(math.radians(angle + 30))} Z") + elements.append(self._draw_path(path, accent_color)) + + elif special_feature == "tusks" and animal == "elephant": + # Elephant tusks + path_left = (f"M {x-size*0.15} {y+size*0.1} " + f"Q {x-size*0.3} {y+size*0.3}, {x-size*0.35} {y+size*0.5}") + elements.append(self._draw_path(path_left, "#FFFFF0", stroke="#F5F5DC", stroke_width=size*0.04)) + + path_right = (f"M {x+size*0.15} {y+size*0.1} " + f"Q {x+size*0.3} {y+size*0.3}, {x+size*0.35} {y+size*0.5}") + elements.append(self._draw_path(path_right, "#FFFFF0", stroke="#F5F5DC", stroke_width=size*0.04)) + + elif special_feature == "antlers" and animal == "deer": + # Deer antlers + # Left antler + path_left = (f"M {x-size*0.15} {y-size*0.2} " + f"L {x-size*0.3} {y-size*0.45} " + f"L {x-size*0.4} {y-size*0.4} " + f"M {x-size*0.3} {y-size*0.45} " + f"L {x-size*0.2} {y-size*0.5}") + elements.append(self._draw_path(path_left, "none", stroke=accent_color, stroke_width=size*0.03)) + + # Right antler + path_right = (f"M {x+size*0.15} {y-size*0.2} " + f"L {x+size*0.3} {y-size*0.45} " + f"L {x+size*0.4} {y-size*0.4} " + f"M {x+size*0.3} {y-size*0.45} " + f"L {x+size*0.2} {y-size*0.5}") + elements.append(self._draw_path(path_right, "none", stroke=accent_color, stroke_width=size*0.03)) + + elif special_feature == "bushy_tail" and animal == "squirrel": + # Squirrel bushy tail + path = (f"M {x+size*0.1} {y+size*0.2} " + f"Q {x+size*0.5} {y}, {x+size*0.3} {y-size*0.3} " + f"Q {x+size*0.4} {y-size*0.4}, {x+size*0.5} {y-size*0.35} " + f"Q {x+size*0.45} {y-size*0.25}, {x+size*0.6} {y-size*0.3} " + f"Q {x+size*0.55} {y-size*0.1}, {x+size*0.4} {y+size*0.1} " + f"Q {x+size*0.3} {y+size*0.3}, {x+size*0.1} {y+size*0.2} Z") + elements.append(self._draw_path(path, accent_color)) + + elif special_feature == "brush_tail" and animal in ["fox", "wolf"]: + # Fox/wolf brush tail + path = (f"M {x+size*0.1} {y+size*0.2} " + f"Q {x+size*0.4} {y+size*0.1}, {x+size*0.5} {y-size*0.1} " + f"Q {x+size*0.6} {y-size*0.2}, {x+size*0.7} {y-size*0.1} " + f"Q {x+size*0.65} {y}, {x+size*0.6} {y+size*0.1} " + f"Q {x+size*0.5} {y+size*0.2}, {x+size*0.3} {y+size*0.3} Z") + elements.append(self._draw_path(path, face_color)) + # Tail tip + elements.append(self._draw_ellipse(x + size * 0.6, y - size * 0.05, size * 0.12, size * 0.08, accent_color)) + + elif special_feature == "bib" and animal == "penguin": + # Penguin bib/chest + path = (f"M {x-size*0.2} {y} " + f"Q {x} {y+size*0.4}, {x+size*0.2} {y} " + f"Q {x} {y+size*0.1}, {x-size*0.2} {y} Z") + elements.append(self._draw_path(path, "#FFFFFF")) + + elif special_feature == "feather_tufts" and animal == "owl": + # Owl feather tufts + path_left = (f"M {x-size*0.1} {y-size*0.3} " + f"Q {x-size*0.15} {y-size*0.45}, {x-size*0.05} {y-size*0.5}") + elements.append(self._draw_path(path_left, face_color, stroke="#000000", stroke_width=size*0.01)) + + path_right = (f"M {x+size*0.1} {y-size*0.3} " + f"Q {x+size*0.15} {y-size*0.45}, {x+size*0.05} {y-size*0.5}") + elements.append(self._draw_path(path_right, face_color, stroke="#000000", stroke_width=size*0.01)) + + elif special_feature == "spikes" and animal == "hedgehog": + # Hedgehog spikes + for i in range(12): + angle = i * 30 + inner_x = x + size * 0.3 * math.cos(math.radians(angle)) + inner_y = y + size * 0.3 * math.sin(math.radians(angle)) + outer_x = x + size * 0.5 * math.cos(math.radians(angle)) + outer_y = y + size * 0.5 * math.sin(math.radians(angle)) + + path = f"M {inner_x} {inner_y} L {outer_x} {outer_y}" + elements.append(self._draw_path(path, "none", stroke=accent_color, stroke_width=size*0.03)) + + elif special_feature == "cheek_patches" and animal == "monkey": + # Monkey cheek patches + elements.append(self._draw_circle(x - size * 0.25, y + size * 0.1, size * 0.12, accent_color)) + elements.append(self._draw_circle(x + size * 0.25, y + size * 0.1, size * 0.12, accent_color)) + + return "\n".join(elements) + + def generate_avatar(self, animal: Optional[str] = None, color_palette: str = "natural", + face_shape: Optional[str] = None, eye_style: Optional[str] = None, + ear_style: Optional[str] = None, nose_style: Optional[str] = None, + expression: Optional[str] = None, special_feature: Optional[str] = None, + size: int = 500) -> str: + """ + Generate an animal avatar with the specified parameters. + + Args: + animal: Animal type (e.g., "cat", "dog"). If None, a random animal is selected. + color_palette: Color palette to use (e.g., "natural", "pastel", "vibrant", "mono"). + face_shape: Shape of the face. If None, a random shape is selected. + eye_style: Style of the eyes. If None, a random style is selected. + ear_style: Style of the ears. If None, a random style is selected for the animal. + nose_style: Style of the nose. If None, a random style is selected. + expression: Facial expression. If None, a random expression is selected. + special_feature: Special feature to add. If None, a random feature is selected for the animal. + size: Size of the avatar in pixels. + + Returns: + SVG string representation of the generated avatar. + """ + # Select random animal if not specified + if animal is None or animal not in self.ANIMALS: + animal = random.choice(self.ANIMALS) + + # Select random options if not specified + if face_shape is None or face_shape not in self.FACE_SHAPES: + face_shape = random.choice(self.FACE_SHAPES) + + if eye_style is None or eye_style not in self.EYE_STYLES: + eye_style = random.choice(self.EYE_STYLES) + + if ear_style is None: + ear_style = self._get_ear_style(animal) + + if nose_style is None or nose_style not in self.NOSE_STYLES: + nose_style = random.choice(self.NOSE_STYLES) + + if expression is None or expression not in self.EXPRESSIONS: + expression = random.choice(self.EXPRESSIONS) + + if special_feature is None: + special_feature = self._get_special_feature(animal) + + # Get colors + colors = self._get_colors(animal, color_palette) + face_color = random.choice(colors) + + # Make sure accent color is different from face color + remaining_colors = [c for c in colors if c != face_color] + if not remaining_colors: + remaining_colors = ["#000000", "#FFFFFF"] + accent_color = random.choice(remaining_colors) + + # Ensure inner ear color is different from face color + inner_ear_color = random.choice(remaining_colors) + + # Eye color options + eye_colors = ["#000000", "#331800", "#0000FF", "#008000", "#FFA500", "#800080"] + eye_color = random.choice(eye_colors) + + # Nose color options based on animal + if animal in ["dog", "cat", "fox", "wolf", "bear", "panda", "koala", "tiger", "lion"]: + nose_color = "#000000" + else: + nose_color = accent_color + + # Center coordinates + x, y = size / 2, size / 2 + + # Generate SVG elements + elements = [] + + # Draw the face first + elements.append(self._draw_face(animal, face_shape, face_color, x, y, size)) + + # Draw special features behind the face if needed + if special_feature in ["mane", "spikes"]: + elements.append(self._draw_special_features(animal, special_feature, face_color, + accent_color, x, y, size)) + + # Draw ears + elements.append(self._draw_ears(animal, ear_style, face_color, inner_ear_color, x, y, size)) + + # Draw eyes + elements.append(self._draw_eyes(eye_style, expression, eye_color, x, y, size)) + + # Draw nose + elements.append(self._draw_nose(animal, nose_style, nose_color, x, y, size)) + + # Draw mouth + elements.append(self._draw_mouth(expression, x, y, size)) + + # Draw special features that should be in front + if special_feature not in ["mane", "spikes"]: + elements.append(self._draw_special_features(animal, special_feature, face_color, + accent_color, x, y, size)) + + # Assemble SVG + svg_content = '\n'.join(elements) + svg = (f'<svg viewBox="0 0 {size} {size}" xmlns="http://www.w3.org/2000/svg">\n' + f'{svg_content}\n' + f'</svg>') + + return svg + + def get_avatar_options(self) -> Dict[str, List[str]]: + """Return all available avatar options.""" + return { + "animals": self.ANIMALS, + "color_palettes": list(self.COLOR_PALETTES.keys()), + "face_shapes": self.FACE_SHAPES, + "eye_styles": self.EYE_STYLES, + "ear_styles": {animal: styles for animal, styles in self.EAR_STYLES.items()}, + "nose_styles": self.NOSE_STYLES, + "expressions": self.EXPRESSIONS, + "special_features": {animal: features for animal, features in self.SPECIAL_FEATURES.items()} + } + + def generate_random_avatar(self, size: int = 500) -> str: + """Generate a completely random avatar.""" + return self.generate_avatar(size=size) + +def generate_avatar_with_options(options: Dict) -> str: + """Generate an avatar with the given options.""" + generator = AnimalAvatarGenerator(seed=options.get("seed")) + + return generator.generate_avatar( + animal=options.get("animal"), + color_palette=options.get("color_palette", "natural"), + face_shape=options.get("face_shape"), + eye_style=options.get("eye_style"), + ear_style=options.get("ear_style"), + nose_style=options.get("nose_style"), + expression=options.get("expression"), + special_feature=options.get("special_feature"), + size=options.get("size", 500) + ) + +def list_avatar_options() -> Dict[str, List[str]]: + """Return all available avatar options.""" + generator = AnimalAvatarGenerator() + return generator.get_avatar_options() + +def create_avatar_app(): + """Command-line interface for the avatar generator.""" + parser = argparse.ArgumentParser(description="Generate animal avatars") + parser.add_argument("--animal", help="Animal type", choices=AnimalAvatarGenerator.ANIMALS) + parser.add_argument("--color-palette", help="Color palette", default="natural", + choices=["natural", "pastel", "vibrant", "mono"]) + parser.add_argument("--face-shape", help="Face shape", choices=AnimalAvatarGenerator.FACE_SHAPES) + parser.add_argument("--eye-style", help="Eye style", choices=AnimalAvatarGenerator.EYE_STYLES) + parser.add_argument("--ear-style", help="Ear style") + parser.add_argument("--nose-style", help="Nose style", choices=AnimalAvatarGenerator.NOSE_STYLES) + parser.add_argument("--expression", help="Expression", choices=AnimalAvatarGenerator.EXPRESSIONS) + parser.add_argument("--special-feature", help="Special feature") + parser.add_argument("--size", help="Size in pixels", type=int, default=500) + parser.add_argument("--seed", help="Random seed for reproducibility", type=int) + parser.add_argument("--output", help="Output file path", default="avatar.svg") + parser.add_argument("--list-options", help="List all available options", action="store_true") + + args = parser.parse_args() + + if args.list_options: + options = list_avatar_options() + print(json.dumps(options, indent=2)) + return + + generator = AnimalAvatarGenerator(seed=args.seed) + + svg = generator.generate_avatar( + animal=args.animal, + color_palette=args.color_palette, + face_shape=args.face_shape, + eye_style=args.eye_style, + ear_style=args.ear_style, + nose_style=args.nose_style, + expression=args.expression, + special_feature=args.special_feature, + size=args.size + ) + + with open(args.output, "w") as f: + f.write(svg) + + print(f"Avatar saved to {args.output}") + +if __name__ == "__main__": + create_avatar_app()