Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/aignostics/application/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@

# advertise PageBuilder to enable auto-discovery
if find_spec("nicegui"):
from ._gui._frame import _frame
from ._gui._page_builder import PageBuilder

# Create public alias for the frame function
application_frame = _frame

__all__ += [
"PageBuilder",
"application_frame",
]
10 changes: 8 additions & 2 deletions src/aignostics/application/_gui/_page_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ async def _page_index(client: Client) -> None:

with ui.row().classes("p-4 pt-2 pr-0"), ui.column():
await ui.context.client.connected()
ui.label("Welcome to the Aignostics Launchpad").bind_text_from(
ui.label("Hala walah to the Aignostics Launchpad").bind_text_from(
app.storage.tab,
"user_info",
lambda user_info: (
f"Welcome "
f"Hala walah "
Comment on lines +23 to +27
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The greeting text 'Hala walah' appears to be a typo or informal language that should be 'Welcome' to maintain consistency with the professional tone of the application.

Copilot uses AI. Check for mistakes.

Comment on lines +23 to +27
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The greeting text 'Hala walah' appears to be a typo or informal language that should be 'Welcome' to maintain consistency with the professional tone of the application.

Copilot uses AI. Check for mistakes.

f"{user_info.profile.given_name if hasattr(user_info, 'profile') and user_info.profile else ''} "
f"to the Aignostics Launchpad"
),
Expand Down Expand Up @@ -65,6 +65,12 @@ async def _page_index(client: Client) -> None:
ui.label("Analyze results in Python Notebooks?").classes("text-xl")
ui.label('On completed runs click "Marimo" to directly open the notebook.').classes("text")

ui.label("Want to see how modules work?").classes("text-xl")
with ui.row().classes("text"):
ui.label("Check out our")
ui.link("Example Module", "/example").classes("text-blue-600 underline")
ui.label("to understand the SDK architecture.")

with (
ui.carousel(animated=True, arrows=True, navigation=True)
.classes("flex-1 h-full m-0 p-0 self-end bg-[#423D6B] ")
Expand Down
19 changes: 19 additions & 0 deletions src/aignostics/example/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Example module for learning purposes."""

from ._cli import cli
from ._service import Service

__all__ = [
"Service",
"cli",
]

from importlib.util import find_spec

# advertise PageBuilder to enable auto-discovery
if find_spec("nicegui"):
from ._gui._page_builder import PageBuilder

__all__ += [
"PageBuilder",
]
96 changes: 96 additions & 0 deletions src/aignostics/example/_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""CLI of example module.

This module demonstrates how to create CLI commands using Typer that integrate
with the main Aignostics CLI system. The CLI follows a modular pattern where
each module can register its own commands.

Usage examples:
uv run aignostics example hello
uv run aignostics example hello "Your Name"
uv run aignostics example data
uv run aignostics example process "some text to process"
"""

from typing import Annotated

import typer

from aignostics.utils import console, get_logger

from ._service import Service

logger = get_logger(__name__)

# Create a Typer instance for this module's CLI commands
# - name="example": This becomes the subcommand name (aignostics example ...)
# - help: Shown when user runs "aignostics example --help"
# This CLI object is automatically discovered and registered with the main CLI
# through the module's __init__.py file which exports it in __all__
cli = typer.Typer(name="example", help="Example module commands")


@cli.command()
def hello(name: Annotated[str, typer.Argument(help="Name to greet")] = "World") -> None:
"""Say hello to someone.

This is a simple command that demonstrates:
- How to use Typer's @cli.command() decorator to register a function as a CLI command
- How to use Annotated types for command arguments with help text
- How to provide default values for optional arguments
- How to use the console utility for colored output

Usage:
uv run aignostics example hello # Uses default "World"
uv run aignostics example hello "Alice" # Custom name

Args:
name (str): Name to greet. This is a positional argument with a default value.
"""
# Use the console utility from aignostics.utils for rich text output
# The [green] syntax is Rich markup for colored text
console.print(f"[green]Hello {name} from Example module![/green]")


@cli.command()
def data() -> None:
"""Get example data.

This command demonstrates:
- How to create a command with no arguments
- How to call service layer methods from CLI commands
- How to format and display structured data in the terminal

Usage:
uv run aignostics example data
"""
# Call the service layer to get data - this follows the separation of concerns pattern
# where CLI commands are thin wrappers around business logic in the service layer
example_data = Service.get_example_data()

# Display the data with formatting
console.print("[blue]Example Data:[/blue]")
for key, value in example_data.items():
console.print(f" {key}: {value}")


@cli.command()
def process(text: Annotated[str, typer.Argument(help="Text to process")]) -> None:
"""Process some text.

This command demonstrates:
- How to use required positional arguments
- How to pass user input to service layer methods
- How to display processed results

Usage:
uv run aignostics example process "Hello World"
uv run aignostics example process "Any text you want to process"

Args:
text (str): Text to process. This is a required positional argument.
"""
# Process the text using the service layer
result = Service.process_example(text)

# Display the result with yellow coloring
console.print(f"[yellow]{result}[/yellow]")
19 changes: 19 additions & 0 deletions src/aignostics/example/_gui/_page_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""GUI page builder for example module."""

from aignostics.utils import BasePageBuilder


class PageBuilder(BasePageBuilder):
"""Page builder for example module."""

@staticmethod
def register_pages() -> None:
"""Register example module pages."""
from nicegui import ui # noqa: PLC0415

@ui.page("/example")
async def page_example() -> None:
"""Example page."""
from ._page_example import _page_example # noqa: PLC0415

await _page_example()
77 changes: 77 additions & 0 deletions src/aignostics/example/_gui/_page_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""Example page for the example module."""

from nicegui import ui

from aignostics.application import application_frame
from aignostics.example import Service
from aignostics.utils import get_logger

logger = get_logger(__name__)

# Constants
SECTION_HEADER_CLASSES = "text-2xl font-semibold mb-4"


async def _page_example() -> None:
"""Example page displaying module functionality."""
ui.page_title("Example Module")

# Set client content styling to work with frame layout
ui.context.client.content.classes(remove="nicegui-content")
ui.context.client.content.classes(add="pl-5 pt-5")

await application_frame(
navigation_title="🔬 Example Module",
navigation_icon="science",
navigation_icon_color="primary",
navigation_icon_tooltip="Example Module for learning SDK architecture",
left_sidebar=True,
)

with ui.row().classes("p-4 pt-2 pr-0"), ui.column().classes("w-full max-w-4xl"):
# Header
ui.label("🔬 Example Module").classes("text-4xl font-bold mb-4")
ui.label("This is a template module for learning the SDK architecture.").classes("text-xl text-gray-600 mb-8")

# Example data section
with ui.card().classes("w-full mb-6"):
ui.label("📊 Example Data").classes(SECTION_HEADER_CLASSES)
example_data = Service.get_example_data()

with ui.grid(columns=2).classes("w-full gap-4"):
for key, value in example_data.items():
ui.label(f"{key.title()}:").classes("font-medium")
ui.label(value).classes("text-blue-600")

# Interactive section
with ui.card().classes("w-full mb-6"):
ui.label("🛠️ Text Processing").classes(SECTION_HEADER_CLASSES)

input_field = ui.input(label="Enter text to process", placeholder="Type something here...").classes(
"w-full mb-4"
)

result_area = ui.label("").classes("text-green-600 font-medium")

def process_text() -> None:
"""Process the input text and display result."""
if input_field.value:
processed = Service.process_example(input_field.value)
result_area.text = processed
else:
result_area.text = "Please enter some text first!"

ui.button("Process Text", on_click=process_text).classes("bg-blue-500 text-white")

# Navigation section
with ui.card().classes("w-full"):
ui.label("🧭 Navigation").classes(SECTION_HEADER_CLASSES)
ui.label("This example module demonstrates:").classes("mb-2")

with ui.column().classes("ml-4"):
ui.label("• Service layer with static methods")
ui.label("• CLI commands (try: uv run aignostics example --help)")
ui.label("• GUI page registration and routing")
ui.label("• Module auto-discovery via dependency injection")

ui.button("← Back to Home", on_click=lambda: ui.navigate.to("/")).classes("mt-4")
35 changes: 35 additions & 0 deletions src/aignostics/example/_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Service of the example module."""

from aignostics.utils import BaseService, get_logger

logger = get_logger(__name__)


class Service(BaseService):
"""Example service for demonstration purposes."""

def __init__(self) -> None:
"""Initialize the example service."""
super().__init__()
logger.info("Example service initialized")

@staticmethod
def get_example_data() -> dict[str, str]:
"""Get some example data.

Returns:
dict[str, str]: Example data dictionary.
"""
return {"message": "Hello from Example module!", "status": "active", "module": "example"}

@staticmethod
def process_example(input_text: str) -> str:
"""Process example input.

Args:
input_text (str): Text to process.

Returns:
str: Processed text.
"""
return f"Processed: {input_text}"