Skip to content

Commit

Permalink
Merge pull request #228 from burningmantech/reflex
Browse files Browse the repository at this point in the history
Add Reflex app
  • Loading branch information
wsanchez authored Feb 7, 2025
2 parents f241fa2 + e1fbb95 commit 40d7309
Show file tree
Hide file tree
Showing 35 changed files with 1,413 additions and 406 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,12 @@ jobs:
python-version: ["3.12"] # Versions to test with coverage
tox-prefix: ["coverage"]
optional: [false]
include:
# Test next Python version but allow it to fail
- os: "ubuntu-latest"
python-version: "3.13"
optional: true
tox-prefix: "test"
# include:
# # Test next Python version but allow it to fail
# - os: "ubuntu-latest"
# python-version: "3.13"
# optional: true
# tox-prefix: "test"

steps:

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ __pycache__/
/dist/
/htmlcov/
/htmldocs/
/reflex_app/.web/
/rtx.sqlite
/src/*.egg-info/
/wheels/
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ repos:
- id: check-json
- id: check-merge-conflict
- id: check-shebang-scripts-are-executable
- id: check-symlinks
# - id: check-symlinks
- id: check-toml
- id: check-vcs-permalinks
- id: check-xml
Expand Down
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
exclude reflex_app/data
include .coveragerc
include .pre-commit-config.yaml
include codecov.yml
Expand All @@ -6,6 +7,9 @@ include COPYRIGHT.txt
include LICENSE.txt
include mypy.ini
include pyproject.toml
include reflex_app/assets/favicon.ico
include src/transmissions/py.typed
include src/transmissions/store/sqlite/schema/*.sqlite
include tox.ini
include uv.lock
recursive-include reflex_app *.py
6 changes: 6 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ ignore_missing_imports = True
[mypy-hypothesis.*]
ignore_missing_imports = True

[mypy-reflex]
ignore_missing_imports = True

[mypy-reflex_ag_grid]
ignore_missing_imports = True

[mypy-setuptools]
ignore_missing_imports = True

Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ readme = "README.rst"
license = {file = "LICENSE.txt"}

# Requirements
requires-python = ">=3.12"
requires-python = ">=3.12,<3.13"
dependencies = [
"arrow==1.3.0",
"attrs==25.1.0",
Expand All @@ -22,6 +22,8 @@ dependencies = [
"openai-whisper==20240930; sys_platform!='darwin' or platform_machine!='x86_64'",
"pydub==0.25.1",
"pyobjc==11.0; sys_platform=='darwin'",
"reflex==0.6.8",
"reflex-ag-grid==0.0.10",
"rich==13.9.4",
"simpleaudio==1.0.4; sys_platform=='darwin'",
"textual==1.0.0",
Expand Down
13 changes: 13 additions & 0 deletions reflex_app/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
Transmissions Reflex Application
"""

from .app import searchIndexFactory, storeFactory
from .pages import transmissionsListPage


__all__ = [
"searchIndexFactory",
"storeFactory",
"transmissionsListPage",
]
71 changes: 71 additions & 0 deletions reflex_app/app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""
Application
"""

from os import environ
from pathlib import Path

from reflex import App
from twisted.logger import Logger

from transmissions.ext.click import readConfig
from transmissions.ext.logger import startLogging
from transmissions.run._search import searchIndexFactoryFromConfig # FIXME: private
from transmissions.run._store import storeFactoryFromConfig # FIXME: private
from transmissions.search import TransmissionsIndex
from transmissions.store import TXDataStore


log = Logger()


class StoreFactory:
"""
Factory for data store.
"""

_store: TXDataStore | None = None

async def store(self) -> TXDataStore:
"""
Get and cache the data store.
"""
if self._store is None:
log.info("Initializing data store...")
storeFactory = storeFactoryFromConfig(configuration, create=False)
self._store = await storeFactory()

return self._store


class SearchIndexFactory:
"""
Factory for search index.
"""

_index: TransmissionsIndex | None = None

async def index(self, store: TXDataStore) -> TransmissionsIndex:
"""
Get and cache the search index.
"""
if self._index is None:
log.info("Initializing search index...")
searchIndexFactory = searchIndexFactoryFromConfig(configuration)
self._index = await searchIndexFactory(store)

return self._index


startLogging()

defaultConfigPath = Path("~/.rtx.toml") # FIXME: Not DRY; see _command.py
fileName = environ.get("CONFIG", defaultConfigPath)

log.info("Reading configuration file: {file}", file=fileName)
configuration = readConfig(Path(fileName))

storeFactory = StoreFactory()
searchIndexFactory = SearchIndexFactory()

app = App()
10 changes: 10 additions & 0 deletions reflex_app/app/model/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
Reflex-compatible data models
"""

from ._transmission import RXTransmission


__all__ = [
"RXTransmission",
]
47 changes: 47 additions & 0 deletions reflex_app/app/model/_transmission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import ClassVar, Self, overload

from reflex import Base

from transmissions.model import Transmission, TZInfo


class RXTransmission(Base):
"""
Reflex model for Transmission
"""

dateTimeFormat: ClassVar[str] = "%y-%m-%d %H:%M:%S%z"

@overload
@classmethod
def fromTransmission(cls, transmission: Transmission) -> Self: ...

@overload
@classmethod
def fromTransmission(cls, transmission: None) -> None: ...

@classmethod
def fromTransmission(cls, transmission: Transmission | None) -> Self | None:
if transmission is None:
return None
return cls(
startTime=transmission.startTime.astimezone(TZInfo.PDT.value).strftime(
cls.dateTimeFormat
),
eventID=transmission.eventID,
station=transmission.station,
system=transmission.system,
channel=transmission.channel,
duration=(
transmission.duration.total_seconds() if transmission.duration else None
),
transcription=transmission.transcription,
)

startTime: str
eventID: str
station: str
system: str
channel: str
duration: float | None
transcription: str | None
10 changes: 10 additions & 0 deletions reflex_app/app/pages/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
Reflex page components
"""

from ._list import transmissionsListPage


__all__ = [
"transmissionsListPage",
]
10 changes: 10 additions & 0 deletions reflex_app/app/pages/_list/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
Transmissions List Page
"""

from ._page import transmissionsListPage


__all__ = [
"transmissionsListPage",
]
55 changes: 55 additions & 0 deletions reflex_app/app/pages/_list/_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Transmissions filters
"""

from reflex import Component, form, hstack, input, select, vstack

from ._state import State


def transmissionsFilters() -> Component:
"""
Transmissions filters
"""
return (
form(
vstack(
hstack(
form.field(
form.label("Event"),
select(
State.events,
value=State.selectedEvent,
on_change=State.eventSelected,
label="Event",
),
),
form.field(
form.label("Start"),
input(
type="datetime-local",
name="start_time",
placeholder="Start Time",
value=State.startTime,
on_change=State.startTimeEdited,
),
),
form.field(
form.label("End"),
input(
type="datetime-local",
name="end_time",
value=State.endTime,
on_change=State.endTimeEdited,
),
),
),
input(
type="search",
name="search_text",
placeholder="Search…",
on_blur=State.searchEdited,
),
),
),
)
61 changes: 61 additions & 0 deletions reflex_app/app/pages/_list/_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Transmission Info
"""

from reflex import (
Component,
audio,
blockquote,
card,
code,
cond,
divider,
heading,
text,
vstack,
)

from ._state import State


def selectedTransmissionInfo() -> Component:
"""
Information about the selected transmission.
"""
transmission = State.selectedTransmission

return cond(
transmission,
card(
vstack(
heading("Selected Transmission", as_="h2"),
divider(),
text(
"Station ",
code(transmission.station),
" on channel ",
code(transmission.channel),
" at ",
text.strong(transmission.startTime),
),
divider(),
text("Transcript:"),
blockquote(transmission.transcription),
divider(),
audio(
url=State.selectedTransmissionAudioURL,
width="100%",
height="32px",
),
width="100%",
),
width="100%",
),
card(
vstack(
text("No transmission selected"),
width="100%",
),
width="100%",
),
)
Loading

0 comments on commit 40d7309

Please sign in to comment.