Skip to content

Commit

Permalink
feat: pass strategy from python to component
Browse files Browse the repository at this point in the history
  • Loading branch information
voznik committed Jul 15, 2024
1 parent 3a20440 commit f12459e
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 79 deletions.
10 changes: 5 additions & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
"search.showLineNumbers": true,
"debug.allowBreakpointsEverywhere": true,
"workbench.colorCustomizations": {
"statusBar.background": "#8d2089",
"statusBar.background": "#631661",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#b729b1",
"sash.hoverBorder": "#b729b1",
"statusBarItem.remoteBackground": "#8d2089",
"statusBarItem.hoverBackground": "#8d1f8a",
"sash.hoverBorder": "#8d1f8a",
"statusBarItem.remoteBackground": "#631661",
"statusBarItem.remoteForeground": "#e7e7e7"
},
"peacock.color": "#8d2089",
"peacock.color": "#631661",
"python.defaultInterpreterPath": "/home/voznik/.cache/pypoetry/virtualenvs/streamlit-rxdb-dataframe-zuiqvGqO-py3.10",
"flake8.args": ["--max-line-length=100"],
"flake8.importStrategy": "fromEnvironment",
Expand Down
60 changes: 41 additions & 19 deletions example.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import json
import os
from typing import Dict, List
import streamlit as st
from streamlit.runtime.caching import cache_data

# import json
# from streamlit.runtime.caching import cache_data
from textcomplete import (
Textcomplete,
TextcompleteOption,
TextcompleteResult,
StrategyProps,
textcomplete,
)
Expand All @@ -15,32 +14,55 @@
current_dir = os.path.dirname(os.path.abspath(__file__))
data_dir = os.path.join(current_dir, "textcomplete/frontend/public/assets/data")

if "txt" not in st.session_state:
st.session_state["txt"] = "Hello, this is textcomplete demo @mr"

def on_select(value):
print(value)
original_label: str = "ST Text Area"


original_label = "ST Text Area"
def on_change():
print(st.session_state.txt)

txt = st.text_area(
value="""It was the best of times, it was the worst of times,
it was the age of wisdom,
it was the age of foolishness,
it was the epoch of belief,
it was the epoch of incredulity,
it was the season of Light,
it was the season of Darkness,
it was the spring of hope,
it was the winter of despair (...)""",
key="st_text_area_1",

txt: str = st.text_area(
label=original_label,
value=st.session_state.txt,
key="st_text_area_1",
on_change=on_change,
)

st.write(f"You wrote {len(txt)} characters.")

strategy = StrategyProps(
id="userFullName",
match="\\B@(\\w*)$",
search="""async (term, callback) => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await response.json();
const filteredUsers = users.filter(user =>
`${user.name}`.toLowerCase().includes(term.toLowerCase())
);
callback(filteredUsers.map(user => [user.name]));
}
""",
replace="""([fullName]) => `${fullName}`""",
template="""([fullName]) => `🧑🏻 ${fullName}`""",
)


def on_select(textcomplete_result: TextcompleteResult):
searchResult = textcomplete_result.get("searchResult", "")
text = textcomplete_result.get("text", "")
print(searchResult, text)
st.session_state.txt = text


textcomplete(
area_label=original_label,
strategies=[strategy],
on_select=on_select,
max_count=5,
# style="border: 1px solid #ccc; padding: 10px; border-radius: 5px;",
)

# st.write(f"Textcomplete result: {completed}")
122 changes: 90 additions & 32 deletions textcomplete/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import os

from typing import Any, Callable, Dict, List, Literal, Optional, Union, overload # noqa: F401,E501
import pandas as pd
import streamlit as st
from typing import ( # noqa: F401,E501
Any,
Callable,
Dict,
Generic,
List,
Literal,
Optional,
TypeVar,
TypedDict,
Union,
)
import streamlit.components.v1 as components
from streamlit.elements.widgets.text_widgets import (
from streamlit.elements.widgets.text_widgets import ( # noqa: F401
LabelVisibility,
WidgetCallback,
WidgetArgs,
Expand All @@ -15,8 +23,7 @@
)
from streamlit import session_state as ss # noqa: F401

# from streamlit.elements.widgets import WidgetCallback
from streamlit.runtime.caching import cache_data
from streamlit.runtime.caching import cache_data # noqa: F401

# Create a _RELEASE constant. We'll set this to False while we're developing
# the component, and True when we're ready to package and distribute it.
Expand All @@ -32,35 +39,81 @@
index_js_path = os.path.join(path, "index.js")
_textcomplete = components.declare_component("textcomplete", path=path)

DEFAULT_CONFIG = {
"name": "streamlit-rxdb",
"options": {"storageType": "dexie"},
"multiInstance": False,
"ignoreDuplicate": True,
}

class SearchResult(TypedDict):
data: Any
term: str

class Textcomplete:
def __init__(self, strategies, trigger_immediately=None, option=None, focus=None):
self.trigger_immediately = trigger_immediately
self.strategies = strategies
self.option = option
self.focus = focus


class StrategyProps:
# Define the properties of StrategyProps here
pass
class TextcompleteResult(TypedDict):
searchResult: SearchResult
text: str


class TextcompleteOption:
# Define the properties of TextcompleteOption here
pass
class StrategyProps:
"""_summary_
```typescript
type ReplaceResult = [string, string] | string | null;
export interface StrategyProps<T = any> {
match: RegExp | ((regexp: string | RegExp) => RegExpMatchArray | null);
search: (term: string, callback: SearchCallback<T>, match: RegExpMatchArray) => void;
replace: (data: T) => ReplaceResult;
cache?: boolean;
context?: (text: string) => string | boolean;
template?: (data: T, term: string) => string;
index?: number;
id?: string;
}
```
Args:
match (str): _summary_
search (str): _summary_
replace (str): _summary_
cache (str): _summary_
context (str): _summary_
template (str): _summary_
index (str): _summary_
id (str): _summary_
"""

def __init__(
self,
match: str = None,
search: str = None,
replace: str = None,
cache: bool = False,
context: str = None,
template: str = None,
index: str = None,
id: str = None,
) -> None:
self.match = match
self.search = search
self.replace = replace
self.cache = cache
self.context = context
self.template = template
self.index = index
self.id = id

def to_dict(self) -> Dict[str, Any]:
return {
"match": self.match,
"search": self.search,
"replace": self.replace,
"cache": self.cache,
"context": self.context,
"template": self.template,
"index": self.index,
"id": self.id,
}


def textcomplete(
area_label: str,
height: int | None = None,
strategies: List[StrategyProps],
on_select: WidgetCallback | None = None,
class_name: str = "dropdown-menu textcomplete-dropdown",
item_class_name: str = "textcomplete-item",
Expand All @@ -73,7 +126,7 @@ def textcomplete(
# style: str | None = None, # TODO: CSSStyleDeclaration
dynamic_width: bool = True,
css: str = "",
) -> str | None:
) -> Optional[TextcompleteResult]:
# Call through to our private component function. Arguments we pass here
# will be sent to the frontend, where they'll be available in an "args"
# dictionary.
Expand All @@ -82,7 +135,6 @@ def textcomplete(
# value of the component before the user has interacted with it.

dropdown_option = {
"height": height,
"className": class_name,
"footer": footer,
"header": header,
Expand All @@ -97,13 +149,19 @@ def textcomplete(
},
}

component_value = _textcomplete(
area_label=area_label, dropdown_option=dropdown_option, css=css, default=""
result = _textcomplete(
area_label=area_label,
strategies=[strategy.to_dict() for strategy in strategies],
dropdown_option=dropdown_option,
css=css,
)

if on_select and result:
on_select(result)

# We could modify the value returned from the component if we wanted.
# There's no need to do this in our simple example - but it's an option.
return component_value
return result


__title__ = "Streamlit Textcomplete"
Expand Down
4 changes: 4 additions & 0 deletions textcomplete/frontend/build/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion textcomplete/frontend/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- textarea with alpinejs bindings and config gor @textcomplete/textarea -->
<script src="index.js"></script>
<script src="build/index.js"></script>
<div x-data="{ tagify: null }" x-init="">
<textarea x-ref="streamlit-textcomplete"></textarea>
</div>
Loading

0 comments on commit f12459e

Please sign in to comment.