Skip to content

Instantly share code, notes, and snippets.

@angusdev
Last active April 3, 2025 21:30
Show Gist options
  • Save angusdev/1324d10da61abb7ebe020d76eb95e55d to your computer and use it in GitHub Desktop.
Save angusdev/1324d10da61abb7ebe020d76eb95e55d to your computer and use it in GitHub Desktop.
Postgresql generate insert statement
import uuid
from abc import ABC, abstractmethod
from typing import Any, Optional
import dash
import flask
from dash import Input, Output, State, dcc, html
from dash.development.base_component import Component
class Chatbot(ABC):
"""Chatbot interface."""
@abstractmethod
def get_response(self, user_message: str, session_id: str) -> str:
"""
Generate a markdown formatted response to the user's message.
Args:
user_message (str): The user's message.
session_id (str): The unique session ID for the conversation.
Returns:
str: The chatbot's response in markdown format.
"""
pass
class DashChatbot:
"""A Dash-based user interface for the chatbot."""
def __init__(
self,
chatbot: Chatbot,
title: Optional[str] = None,
logo_url: Optional[str] = None,
greeting_message: Optional[str] = None,
first_question: Optional[str] = None,
input_placeholder: Optional[str] = "Type your message...",
**kwargs,
) -> None:
"""Initialize the DashChatbot with optional configurations."""
self.app = dash.Dash(__name__, server=flask.Flask(__name__), **kwargs)
self.app.title = "Chatbot"
self._chatbot = chatbot
self._title = title
self._logo_url = logo_url
self._greeting_message = greeting_message
self._first_question = first_question
self._input_placeholder = input_placeholder
# Initialize layout and callbacks
self._initialize_layout()
self._initialize_callbacks()
if not self.app.server.secret_key:
self.app.server.secret_key = str(uuid.uuid4())
def _initialize_layout(self) -> None:
"""Initializes the Dash application layout."""
title_section = []
if self._logo_url or self._title:
title_section.append(
html.Div(
className="title-section",
children=[
html.Img(src=self._logo_url, className="chat-logo") if self._logo_url else None,
html.H1(self._title, className="chat-title") if self._title else None,
],
)
)
initial_messages = []
if self._greeting_message:
initial_messages.append(
html.Div([dcc.Markdown(self._greeting_message, className="bot-message")])
)
self.app.layout = html.Div(
className="chat-wrapper",
children=[
*title_section,
html.Div(
id="chat-container",
className="chat-container",
children=initial_messages,
),
html.Div(
className="input-container",
children=[
html.Div(
className="input-row",
children=[
dcc.Textarea(
id="user-input",
placeholder=self._input_placeholder,
className="chat-input",
value=self._first_question,
style={"resize": "none"},
),
],
),
html.Div(
className="button-row",
children=[
html.Div(className="controls-placeholder"), # Placeholder div
html.Button("Send", id="send-button", className="chat-button"),
],
),
],
),
],
)
def _initialize_callbacks(self) -> None:
"""Sets up the Dash application callbacks."""
@self.app.callback(
[Output("chat-container", "children"), Output("user-input", "value")],
Input("send-button", "n_clicks"),
[State("user-input", "value"), State("chat-container", "children")],
)
def update_chat(
n_clicks: Optional[int],
user_message: Optional[str],
chat_history: Optional[list[Component]],
) -> list[Component]:
"""Updates the chat container with new messages and clears the input field."""
if n_clicks is None or not user_message:
return chat_history, ""
chat_history = chat_history or []
# Generate or retrieve the session ID
if "session_id" not in flask.session:
flask.session["session_id"] = str(uuid.uuid4())
session_id = flask.session["session_id"]
# Pass the session ID to the chatbot
bot_response = self._chatbot.get_response(user_message, session_id=session_id)
chat_history.append(
html.Div(
[dcc.Markdown(bot_response, className="bot-message markdown-body")]
)
)
return chat_history, ""
def run(self, **kwargs: dict[str, Any]) -> None:
"""Runs the Dash application server."""
self.app.run(**kwargs)
import time
from dash_chatbot import DashChatbot, Chatbot
class SampleChatbot(Chatbot):
"""A simple chatbot that returns markdown formatted responses."""
def get_response(self, user_message: str) -> str:
"""
Generate a markdown formatted response to the user's message.
Simulates processing time with a delay.
"""
time.sleep(1)
return f"""
### Response
I received your message: *'{user_message}'*
Here's what I can do:
- Parse **markdown**
- Format *text*
- Create lists
"""
if __name__ == "__main__":
chatbot = SampleChatbot()
dash_chatbot = DashChatbot(
chatbot,
title="Welcome to Chatbot",
logo_url="https://images.ctfassets.net/kftzwdyauwt9/2i61iTTUDpWjwTbl6cdJkL/a60bb9ad83127262f5022aabcede01a6/DON-T_add_any_colors_to_the_Blossom.png?w=3840&q=90&fm=webp",
greeting_message="Hello! How can I assist you today?",
first_question="What is your name?",
input_placeholder="Ask me anything...",
)
dash_chatbot.run()
/**
* Initialize chat functionality.
* Sets up event listeners, dynamic input resizing, and response handling.
*/
function initializeChat() {
const elements = {
sendButton: document.getElementById('send-button'),
userInput: document.getElementById('user-input'),
chatContainer: document.getElementById('chat-container')
};
if (!elements.sendButton || !elements.userInput || !elements.chatContainer) {
setTimeout(initializeChat, 100);
return;
}
let isWaitingResponse = false;
/**
* Enable or disable input controls.
* @param {boolean} enabled - Whether to enable the controls.
*/
function setInputsEnabled(enabled) {
elements.userInput.disabled = !enabled;
elements.sendButton.disabled = !enabled;
if (enabled) {
elements.userInput.focus();
}
}
/**
* Add a user message to the chat container.
* @param {string} message - The user's message to display.
*/
function addUserMessage(message) {
const div = document.createElement('div');
div.textContent = message;
div.className = 'user-message';
elements.chatContainer.appendChild(div);
}
/**
* Add a "Thinking..." indicator to the chat container.
*/
function addThinkingMessage() {
const div = document.createElement('div');
div.innerHTML = 'Thinking...';
div.className = 'thinking-message';
div.id = 'thinking-message';
elements.chatContainer.appendChild(div);
div.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
/**
* Dynamically adjust the height of the input field based on its content.
* @param {HTMLTextAreaElement} textarea - The textarea element to adjust.
*/
function adjustTextareaHeight(textarea) {
textarea.style.height = '0'; // Reset height to calculate the new height
if (textarea.value) {
textarea.style.height = `${textarea.scrollHeight}px`; // Set height based on scrollHeight
}
}
/**
* Handle the submission of a user message.
* @param {Event} e - The event object triggered by the user action.
*/
function handleMessage(e) {
if (isWaitingResponse) return;
const message = elements.userInput.value.trim();
if (message) {
isWaitingResponse = true;
setInputsEnabled(false);
addUserMessage(message);
if (e.isTrusted) {
addThinkingMessage();
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
elements.sendButton.dispatchEvent(clickEvent);
}
}
}
/**
* Observe changes in the chat container to handle bot responses.
*/
const observer = new MutationObserver((mutations) => {
const thinkingMsg = document.getElementById('thinking-message');
if (thinkingMsg && thinkingMsg.nextElementSibling) {
const newMessage = thinkingMsg.nextElementSibling;
thinkingMsg.remove();
newMessage.scrollIntoView({ behavior: 'smooth', block: 'start' });
isWaitingResponse = false;
adjustTextareaHeight(elements.userInput);
setInputsEnabled(true);
}
});
observer.observe(elements.chatContainer, { childList: true });
// Event listeners
elements.sendButton.addEventListener('click', handleMessage);
elements.userInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
e.stopPropagation();
handleMessage(e);
}
});
elements.userInput.addEventListener('input', () => {
adjustTextareaHeight(elements.userInput);
});
// Initialize the height of the input field
adjustTextareaHeight(elements.userInput);
}
window.addEventListener('load', initializeChat);
/* Root Variables */
:root {
--system-fonts: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol";
--primary-color: #007bff;
--disabled-color: #6c757d;
--bg-light: #f9f9f9;
--border-color: #ccc;
--user-msg-bg: #eee;
--thinking-msg-bg: #e9ecef;
--disabled-bg: #e9ecef;
--container-width: 800px;
--border-radius: 10px;
--spacing-normal: 10px;
--spacing-large: 20px;
}
/* Import GitHub Markdown CSS */
@import url('https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css');
/* Global Styles */
body {
margin: 0;
padding: 0;
height: 100vh;
font-family: var(--system-fonts);
}
/* Layout */
.chat-wrapper {
height: 100vh;
display: flex;
flex-direction: column;
max-width: var(--container-width);
margin: 0 auto;
position: relative;
}
.chat-container {
padding: var(--spacing-large);
padding-bottom: 140px;
flex: 1;
display: flex;
flex-direction: column;
}
.input-container {
display: flex;
flex-direction: column;
gap: var(--spacing-normal);
position: fixed;
bottom: var(--spacing-large);
left: 50%;
transform: translateX(-50%);
width: 100%;
max-width: var(--container-width);
background: white;
padding: var(--spacing-normal);
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
border-radius: 15px;
z-index: 1000;
}
/* Components */
.title-section {
display: flex;
align-items: center;
margin: var(--spacing-large) 0;
}
.chat-logo {
height: 40px;
margin-right: var(--spacing-normal);
}
.chat-title {
font-size: 24px;
margin: 0;
}
.user-message,
.bot-message,
.thinking-message {
margin-bottom: var(--spacing-normal);
padding: var(--spacing-normal);
border-radius: var(--border-radius);
max-width: 70%;
}
.user-message {
background-color: var(--user-msg-bg);
align-self: flex-end;
width: fit-content;
}
.bot-message {
margin-right: auto;
padding: var(--spacing-normal);
white-space: pre-wrap; /* Preserve formatting for markdown */
}
.thinking-message {
background-color: var(--thinking-msg-bg);
margin-right: auto;
font-style: italic;
color: var(--disabled-color);
}
.input-row {
display: flex;
flex: 1;
}
.chat-input {
flex: 1;
padding: var(--spacing-normal);
min-height: 20px;
max-height: 150px;
overflow-y: auto;
line-height: 1.5;
}
.button-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.controls-placeholder {
flex: 1; /* Fills the remaining space */
}
.chat-button {
padding: var(--spacing-normal) var(--spacing-large);
font-size: 16px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin-left: auto; /* Aligns the button to the right */
}
.chat-button.small {
padding: var(--spacing-normal);
font-size: 14px;
margin-right: var(--spacing-normal);
}
/* States */
.chat-input:disabled {
background-color: var(--disabled-bg);
cursor: not-allowed;
}
.chat-button:disabled {
background-color: var(--disabled-color);
cursor: not-allowed;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment