From 8861b32ae5c80a305ee3fd5bbf1f40018a17408d Mon Sep 17 00:00:00 2001 From: Austin Godber Date: Mon, 15 Sep 2025 08:03:39 -0700 Subject: [PATCH 1/4] add about modal --- src/embeddingbuddy/app.py | 31 +++++++++ .../ui/callbacks/interactions.py | 10 +++ src/embeddingbuddy/ui/components/about.py | 69 +++++++++++++++++++ src/embeddingbuddy/ui/layout.py | 16 ++++- 4 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 src/embeddingbuddy/ui/components/about.py diff --git a/src/embeddingbuddy/app.py b/src/embeddingbuddy/app.py index dae0a47..5bbf165 100644 --- a/src/embeddingbuddy/app.py +++ b/src/embeddingbuddy/app.py @@ -16,11 +16,42 @@ def create_app(): app = dash.Dash( __name__, + title="EmbeddingBuddy", external_stylesheets=[ dbc.themes.BOOTSTRAP, "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css", ], assets_folder=assets_path, + meta_tags=[ + { + "name": "description", + "content": "Interactive embedding visualization tool for exploring high-dimensional vectors through dimensionality reduction techniques like PCA, t-SNE, and UMAP." + }, + { + "name": "author", + "content": "EmbeddingBuddy" + }, + { + "name": "keywords", + "content": "embeddings, visualization, dimensionality reduction, PCA, t-SNE, UMAP, machine learning, data science" + }, + { + "name": "viewport", + "content": "width=device-width, initial-scale=1.0" + }, + { + "property": "og:title", + "content": "EmbeddingBuddy - Interactive Embedding Visualization" + }, + { + "property": "og:description", + "content": "Explore and visualize embedding vectors through interactive 2D/3D plots with multiple dimensionality reduction techniques." + }, + { + "property": "og:type", + "content": "website" + } + ], ) # Allow callbacks to components that are dynamically created in tabs diff --git a/src/embeddingbuddy/ui/callbacks/interactions.py b/src/embeddingbuddy/ui/callbacks/interactions.py index b9130f7..b8191c8 100644 --- a/src/embeddingbuddy/ui/callbacks/interactions.py +++ b/src/embeddingbuddy/ui/callbacks/interactions.py @@ -7,6 +7,16 @@ class InteractionCallbacks: self._register_callbacks() def _register_callbacks(self): + @callback( + Output("about-modal", "is_open"), + [Input("about-button", "n_clicks"), Input("about-modal-close", "n_clicks")], + prevent_initial_call=True, + ) + def toggle_about_modal(about_clicks, close_clicks): + if about_clicks or close_clicks: + return True if about_clicks else False + return False + @callback( [ Output("processed-data", "data", allow_duplicate=True), diff --git a/src/embeddingbuddy/ui/components/about.py b/src/embeddingbuddy/ui/components/about.py new file mode 100644 index 0000000..78e166c --- /dev/null +++ b/src/embeddingbuddy/ui/components/about.py @@ -0,0 +1,69 @@ +from dash import html, dcc +import dash_bootstrap_components as dbc + + +class AboutComponent: + def _get_about_content(self): + return """ +# 🔍 Interactive Embedding Visualization + +EmbeddingBuddy is a modular Python Dash web application for interactive exploration and visualization of embedding vectors through dimensionality reduction techniques (PCA, t-SNE, UMAP). + +## ✨ Features + +- Drag-and-drop NDJSON file upload +- Multiple dimensionality reduction algorithms +- 2D/3D interactive plots with Plotly +- Color coding by categories, subcategories, or tags +- In-browser embedding generation +- OpenSearch integration for data loading + +## 🔧 Supported Algorithms + +- **PCA** (Principal Component Analysis) +- **t-SNE** (t-Distributed Stochastic Neighbor Embedding) +- **UMAP** (Uniform Manifold Approximation and Projection) + +--- + +📂 [View on GitHub](https://github.com/godber/EmbeddingBuddy) + +*Built with: Python, Dash, Plotly, scikit-learn, OpenTSNE, UMAP* + """.strip() + + def create_about_modal(self): + return dbc.Modal( + [ + dbc.ModalHeader( + dbc.ModalTitle("About EmbeddingBuddy"), + close_button=True, + ), + dbc.ModalBody([ + dcc.Markdown( + self._get_about_content(), + className="mb-0" + ) + ]), + dbc.ModalFooter([ + dbc.Button( + "Close", + id="about-modal-close", + color="secondary", + n_clicks=0 + ) + ]), + ], + id="about-modal", + is_open=False, + size="lg", + ) + + def create_about_button(self): + return dbc.Button( + [html.I(className="fas fa-info-circle me-2"), "About"], + id="about-button", + color="outline-info", + size="sm", + n_clicks=0, + className="ms-2" + ) \ No newline at end of file diff --git a/src/embeddingbuddy/ui/layout.py b/src/embeddingbuddy/ui/layout.py index f3d1fea..3958537 100644 --- a/src/embeddingbuddy/ui/layout.py +++ b/src/embeddingbuddy/ui/layout.py @@ -1,16 +1,19 @@ from dash import dcc, html import dash_bootstrap_components as dbc from .components.sidebar import SidebarComponent +from .components.about import AboutComponent class AppLayout: def __init__(self): self.sidebar = SidebarComponent() + self.about = AboutComponent() def create_layout(self): return dbc.Container( [self._create_header(), self._create_main_content()] - + self._create_stores(), + + self._create_stores() + + [self.about.create_about_modal()], fluid=True, ) @@ -19,7 +22,16 @@ class AppLayout: [ dbc.Col( [ - html.H1("EmbeddingBuddy", className="text-center mb-4"), + html.Div( + [ + html.H1("EmbeddingBuddy", className="text-center mb-4 d-inline"), + html.Div( + [self.about.create_about_button()], + className="float-end" + ), + ], + className="d-flex justify-content-between align-items-center" + ), # Load Transformers.js from CDN html.Script( """ -- 2.49.1 From ea01ce596dcd696dd806abaf20141605c55f4885 Mon Sep 17 00:00:00 2001 From: Austin Godber Date: Mon, 15 Sep 2025 08:05:06 -0700 Subject: [PATCH 2/4] update version to 0.5.1 --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c58b602..3aef8ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "embeddingbuddy" -version = "0.5.0" +version = "0.5.1" description = "A Python Dash application for interactive exploration and visualization of embedding vectors through dimensionality reduction techniques." readme = "README.md" requires-python = ">=3.11" diff --git a/uv.lock b/uv.lock index 69e3885..792bdeb 100644 --- a/uv.lock +++ b/uv.lock @@ -412,7 +412,7 @@ wheels = [ [[package]] name = "embeddingbuddy" -version = "0.5.0" +version = "0.5.1" source = { editable = "." } dependencies = [ { name = "dash" }, -- 2.49.1 From 89dcafd3111ddaeaa2340b38b6f1034a00f91666 Mon Sep 17 00:00:00 2001 From: Austin Godber Date: Tue, 16 Sep 2025 08:12:36 -0700 Subject: [PATCH 3/4] ruff --- src/embeddingbuddy/app.py | 23 +++++------------ src/embeddingbuddy/ui/components/about.py | 31 +++++++++++------------ src/embeddingbuddy/ui/layout.py | 9 ++++--- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/embeddingbuddy/app.py b/src/embeddingbuddy/app.py index 5bbf165..ac99c58 100644 --- a/src/embeddingbuddy/app.py +++ b/src/embeddingbuddy/app.py @@ -25,32 +25,23 @@ def create_app(): meta_tags=[ { "name": "description", - "content": "Interactive embedding visualization tool for exploring high-dimensional vectors through dimensionality reduction techniques like PCA, t-SNE, and UMAP." - }, - { - "name": "author", - "content": "EmbeddingBuddy" + "content": "Interactive embedding visualization tool for exploring high-dimensional vectors through dimensionality reduction techniques like PCA, t-SNE, and UMAP.", }, + {"name": "author", "content": "EmbeddingBuddy"}, { "name": "keywords", - "content": "embeddings, visualization, dimensionality reduction, PCA, t-SNE, UMAP, machine learning, data science" - }, - { - "name": "viewport", - "content": "width=device-width, initial-scale=1.0" + "content": "embeddings, visualization, dimensionality reduction, PCA, t-SNE, UMAP, machine learning, data science", }, + {"name": "viewport", "content": "width=device-width, initial-scale=1.0"}, { "property": "og:title", - "content": "EmbeddingBuddy - Interactive Embedding Visualization" + "content": "EmbeddingBuddy - Interactive Embedding Visualization", }, { "property": "og:description", - "content": "Explore and visualize embedding vectors through interactive 2D/3D plots with multiple dimensionality reduction techniques." + "content": "Explore and visualize embedding vectors through interactive 2D/3D plots with multiple dimensionality reduction techniques.", }, - { - "property": "og:type", - "content": "website" - } + {"property": "og:type", "content": "website"}, ], ) diff --git a/src/embeddingbuddy/ui/components/about.py b/src/embeddingbuddy/ui/components/about.py index 78e166c..5720e09 100644 --- a/src/embeddingbuddy/ui/components/about.py +++ b/src/embeddingbuddy/ui/components/about.py @@ -38,20 +38,19 @@ EmbeddingBuddy is a modular Python Dash web application for interactive explorat dbc.ModalTitle("About EmbeddingBuddy"), close_button=True, ), - dbc.ModalBody([ - dcc.Markdown( - self._get_about_content(), - className="mb-0" - ) - ]), - dbc.ModalFooter([ - dbc.Button( - "Close", - id="about-modal-close", - color="secondary", - n_clicks=0 - ) - ]), + dbc.ModalBody( + [dcc.Markdown(self._get_about_content(), className="mb-0")] + ), + dbc.ModalFooter( + [ + dbc.Button( + "Close", + id="about-modal-close", + color="secondary", + n_clicks=0, + ) + ] + ), ], id="about-modal", is_open=False, @@ -65,5 +64,5 @@ EmbeddingBuddy is a modular Python Dash web application for interactive explorat color="outline-info", size="sm", n_clicks=0, - className="ms-2" - ) \ No newline at end of file + className="ms-2", + ) diff --git a/src/embeddingbuddy/ui/layout.py b/src/embeddingbuddy/ui/layout.py index 3958537..963086e 100644 --- a/src/embeddingbuddy/ui/layout.py +++ b/src/embeddingbuddy/ui/layout.py @@ -24,13 +24,16 @@ class AppLayout: [ html.Div( [ - html.H1("EmbeddingBuddy", className="text-center mb-4 d-inline"), + html.H1( + "EmbeddingBuddy", + className="text-center mb-4 d-inline", + ), html.Div( [self.about.create_about_button()], - className="float-end" + className="float-end", ), ], - className="d-flex justify-content-between align-items-center" + className="d-flex justify-content-between align-items-center", ), # Load Transformers.js from CDN html.Script( -- 2.49.1 From 2f458884a259748a497579994c3c9ffb23d1dbee Mon Sep 17 00:00:00 2001 From: Austin Godber Date: Wed, 17 Sep 2025 19:01:51 -0700 Subject: [PATCH 4/4] Add configurable OpenSearch feature and UI improvements - Add MIT license with Austin Godber copyright - Implement optional OpenSearch feature toggle via EMBEDDINGBUDDY_OPENSEARCH_ENABLED - Disable OpenSearch by default in production for security - Add development environment flag to test OpenSearch disable state - Update about modal to open by default with improved content - Reorganize text input component: move model selection below text input - Conditionally show/hide OpenSearch tab and callbacks based on configuration - Update tooltips to reflect OpenSearch availability status --- LICENSE | 21 +++++++++++++ pyproject.toml | 2 +- run_dev.py | 6 ++++ run_prod.py | 3 ++ src/embeddingbuddy/config/settings.py | 3 ++ .../ui/callbacks/data_processing.py | 12 +++++--- src/embeddingbuddy/ui/components/about.py | 30 ++++++++++++++++--- .../ui/components/datasource.py | 12 +++++--- src/embeddingbuddy/ui/components/sidebar.py | 7 ++++- src/embeddingbuddy/ui/components/textinput.py | 6 ++-- 10 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4227710 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Austin Godber - EmbeddingBuddy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/pyproject.toml b/pyproject.toml index 3aef8ce..bde3071 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "embeddingbuddy" -version = "0.5.1" +version = "0.6.0" description = "A Python Dash application for interactive exploration and visualization of embedding vectors through dimensionality reduction techniques." readme = "README.md" requires-python = ">=3.11" diff --git a/run_dev.py b/run_dev.py index 525efea..ab124d8 100644 --- a/run_dev.py +++ b/run_dev.py @@ -11,10 +11,16 @@ def main(): # Force development settings os.environ["EMBEDDINGBUDDY_ENV"] = "development" os.environ["EMBEDDINGBUDDY_DEBUG"] = "true" + + # Check for OpenSearch disable flag (optional for testing) + # Set EMBEDDINGBUDDY_OPENSEARCH_ENABLED=false to test without OpenSearch + opensearch_status = os.getenv("EMBEDDINGBUDDY_OPENSEARCH_ENABLED", "true") + opensearch_enabled = opensearch_status.lower() == "true" print("🚀 Starting EmbeddingBuddy in development mode...") print("📁 Auto-reload enabled - changes will trigger restart") print("🌐 Server will be available at http://127.0.0.1:8050") + print(f"🔍 OpenSearch: {'Enabled' if opensearch_enabled else 'Disabled'}") print("âšī¸ Press Ctrl+C to stop") app = create_app() diff --git a/run_prod.py b/run_prod.py index 0ee464d..4ccce34 100644 --- a/run_prod.py +++ b/run_prod.py @@ -13,6 +13,9 @@ def main(): # Force production settings os.environ["EMBEDDINGBUDDY_ENV"] = "production" os.environ["EMBEDDINGBUDDY_DEBUG"] = "false" + # Disable OpenSearch by default in production (can be overridden by setting env var) + if "EMBEDDINGBUDDY_OPENSEARCH_ENABLED" not in os.environ: + os.environ["EMBEDDINGBUDDY_OPENSEARCH_ENABLED"] = "false" print("🚀 Starting EmbeddingBuddy in production mode...") print(f"âš™ī¸ Workers: {AppSettings.GUNICORN_WORKERS}") diff --git a/src/embeddingbuddy/config/settings.py b/src/embeddingbuddy/config/settings.py index 1c345c5..c697966 100644 --- a/src/embeddingbuddy/config/settings.py +++ b/src/embeddingbuddy/config/settings.py @@ -85,6 +85,9 @@ class AppSettings: GUNICORN_KEEPALIVE = int(os.getenv("GUNICORN_KEEPALIVE", "5")) # OpenSearch Configuration + OPENSEARCH_ENABLED = ( + os.getenv("EMBEDDINGBUDDY_OPENSEARCH_ENABLED", "True").lower() == "true" + ) OPENSEARCH_DEFAULT_SIZE = 100 OPENSEARCH_SAMPLE_SIZE = 5 OPENSEARCH_CONNECTION_TIMEOUT = 30 diff --git a/src/embeddingbuddy/ui/callbacks/data_processing.py b/src/embeddingbuddy/ui/callbacks/data_processing.py index c52b0c5..09a2fb7 100644 --- a/src/embeddingbuddy/ui/callbacks/data_processing.py +++ b/src/embeddingbuddy/ui/callbacks/data_processing.py @@ -82,19 +82,23 @@ class DataProcessingCallbacks: ) def render_tab_content(active_tab): from ...ui.components.datasource import DataSourceComponent + from ...config.settings import AppSettings datasource = DataSourceComponent() - if active_tab == "opensearch-tab": + if active_tab == "opensearch-tab" and AppSettings.OPENSEARCH_ENABLED: return [datasource.create_opensearch_tab()] elif active_tab == "text-input-tab": return [datasource.create_text_input_tab()] else: return [datasource.create_file_upload_tab()] - # Register callbacks for both data and prompts sections - self._register_opensearch_callbacks("data", self.opensearch_client_data) - self._register_opensearch_callbacks("prompts", self.opensearch_client_prompts) + # Register callbacks for both data and prompts sections (only if OpenSearch is enabled) + if AppSettings.OPENSEARCH_ENABLED: + self._register_opensearch_callbacks("data", self.opensearch_client_data) + self._register_opensearch_callbacks( + "prompts", self.opensearch_client_prompts + ) # Register collapsible section callbacks self._register_collapse_callbacks() diff --git a/src/embeddingbuddy/ui/components/about.py b/src/embeddingbuddy/ui/components/about.py index 5720e09..28aad2d 100644 --- a/src/embeddingbuddy/ui/components/about.py +++ b/src/embeddingbuddy/ui/components/about.py @@ -5,9 +5,31 @@ import dash_bootstrap_components as dbc class AboutComponent: def _get_about_content(self): return """ -# 🔍 Interactive Embedding Visualization +# 🔍 Interactive Embedding Vector Visualization + +EmbeddingBuddy is a web application for interactive exploration and +visualization of embedding vectors through dimensionality reduction techniques +(PCA, t-SNE, UMAP). + +You have two ways to get started: + +1. Generate embeddings directly in the browser if it supports WebGPU. +2. Upload your NDJSON file containing embedding vectors and metadata. + +## Generating Embeddings in Browser + +1. Expand the "Generate Embeddings" section. +2. Input your text data (one entry per line). + 1. Optionally you can use the built in sample data by clicking "Load Sample Data" button. +3. Click "Generate Embeddings" to create vectors using a pre-trained model. + +## NDJSON File Format + +```json +{"id": "doc_001", "embedding": [0.1, -0.3, 0.7, ...], "text": "Sample text content", "category": "news", "subcategory": "politics", "tags": ["election", "politics"]} +{"id": "doc_002", "embedding": [0.2, -0.1, 0.9, ...], "text": "Another example", "category": "review", "subcategory": "product", "tags": ["tech", "gadget"]} +``` -EmbeddingBuddy is a modular Python Dash web application for interactive exploration and visualization of embedding vectors through dimensionality reduction techniques (PCA, t-SNE, UMAP). ## ✨ Features @@ -35,7 +57,7 @@ EmbeddingBuddy is a modular Python Dash web application for interactive explorat return dbc.Modal( [ dbc.ModalHeader( - dbc.ModalTitle("About EmbeddingBuddy"), + dbc.ModalTitle("Welcome to EmbeddingBuddy"), close_button=True, ), dbc.ModalBody( @@ -53,7 +75,7 @@ EmbeddingBuddy is a modular Python Dash web application for interactive explorat ), ], id="about-modal", - is_open=False, + is_open=True, size="lg", ) diff --git a/src/embeddingbuddy/ui/components/datasource.py b/src/embeddingbuddy/ui/components/datasource.py index 2d4c630..a86dd1e 100644 --- a/src/embeddingbuddy/ui/components/datasource.py +++ b/src/embeddingbuddy/ui/components/datasource.py @@ -1,6 +1,7 @@ from dash import dcc, html import dash_bootstrap_components as dbc from .upload import UploadComponent +from embeddingbuddy.config.settings import AppSettings class DataSourceComponent: @@ -9,15 +10,18 @@ class DataSourceComponent: def create_tabbed_interface(self): """Create tabbed interface for different data sources.""" + tabs = [dbc.Tab(label="File Upload", tab_id="file-tab")] + + # Only add OpenSearch tab if enabled + if AppSettings.OPENSEARCH_ENABLED: + tabs.append(dbc.Tab(label="OpenSearch", tab_id="opensearch-tab")) + return dbc.Card( [ dbc.CardHeader( [ dbc.Tabs( - [ - dbc.Tab(label="File Upload", tab_id="file-tab"), - dbc.Tab(label="OpenSearch", tab_id="opensearch-tab"), - ], + tabs, id="data-source-tabs", active_tab="file-tab", ) diff --git a/src/embeddingbuddy/ui/components/sidebar.py b/src/embeddingbuddy/ui/components/sidebar.py index f807e44..ab4c40c 100644 --- a/src/embeddingbuddy/ui/components/sidebar.py +++ b/src/embeddingbuddy/ui/components/sidebar.py @@ -3,6 +3,7 @@ import dash_bootstrap_components as dbc from .upload import UploadComponent from .datasource import DataSourceComponent from .textinput import TextInputComponent +from embeddingbuddy.config.settings import AppSettings class SidebarComponent: @@ -102,6 +103,10 @@ class SidebarComponent: ) def _create_data_sources_item(self): + tooltip_text = "Load existing embeddings: upload files" + if AppSettings.OPENSEARCH_ENABLED: + tooltip_text += " or read from OpenSearch" + return dbc.AccordionItem( [ self.datasource_component.create_error_alert(), @@ -115,7 +120,7 @@ class SidebarComponent: className="fas fa-info-circle text-muted", style={"cursor": "pointer"}, id="load-embeddings-info-icon", - title="Load existing embeddings: upload files or read from OpenSearch", + title=tooltip_text, ), ] ), diff --git a/src/embeddingbuddy/ui/components/textinput.py b/src/embeddingbuddy/ui/components/textinput.py index b19f925..35917d7 100644 --- a/src/embeddingbuddy/ui/components/textinput.py +++ b/src/embeddingbuddy/ui/components/textinput.py @@ -16,14 +16,14 @@ class TextInputComponent: """Create the complete text input interface with model selection and processing options.""" return html.Div( [ - # Model selection section - self._create_model_selection(), - html.Hr(), # Text input section self._create_text_input_area(), # Text action buttons self._create_text_action_buttons(), html.Hr(), + # Model selection section + self._create_model_selection(), + html.Hr(), # Processing options self._create_processing_options(), html.Hr(), -- 2.49.1