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
14 changes: 10 additions & 4 deletions ui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@
from core.feature_registry import FeatureRegistry # noqa: E402
from core.navigation import Navigation # noqa: E402
from features.auto_arena import AutoArenaFeature # noqa: E402
from features.auto_rubric import AutoRubricFeature # noqa: E402

# Import feature modules
from features.grader import GraderFeature # noqa: E402
from shared.components.common import render_footer # noqa: E402
from shared.components.logo import render_logo_and_title # noqa: E402
from shared.i18n import render_language_selector, t # noqa: E402
from shared.i18n import ( # noqa: E402
inject_language_loader,
render_language_selector,
t,
)
from shared.styles.theme import inject_css # noqa: E402

# pylint: enable=wrong-import-position
Expand All @@ -38,9 +43,7 @@
# Add new features here as they are implemented
FeatureRegistry.register(GraderFeature)
FeatureRegistry.register(AutoArenaFeature)
# Future features:
# from features.autorubric import AutoRubricFeature
# FeatureRegistry.register(AutoRubricFeature)
FeatureRegistry.register(AutoRubricFeature)

# ============================================================================
# Page Configuration (must be first Streamlit command)
Expand All @@ -58,6 +61,9 @@ def main() -> None:
# Inject custom CSS
inject_css()

# Load language preference from browser localStorage
inject_language_loader()

# ========================================================================
# Sidebar Configuration
# ========================================================================
Expand Down
14 changes: 8 additions & 6 deletions ui/core/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,29 +38,31 @@ def render_feature_selector() -> str:
st.warning("No features registered")
return ""

# Build options - use display_label property for i18n support
# Note: get_all() returns classes, but display_label is a property that requires instances
# Build options - use stable feature_ids (not translated labels)
feature_ids = [f.feature_id for f in features]
feature_labels = {f.feature_id: FeatureRegistry.get_instance(f.feature_id).display_label for f in features}

# Get default feature id
default_id = FeatureRegistry.get_default_feature_id()

# Use widget key directly for state management
# Widget key for selectbox
widget_key = "_nav_feature_selector"

# Initialize widget state if not exists
if widget_key not in st.session_state:
st.session_state[widget_key] = default_id

# Ensure current value is valid
# Ensure current value is valid (in case features changed)
if st.session_state[widget_key] not in feature_ids:
st.session_state[widget_key] = default_id

# Get previous value for lifecycle hooks
previous_id = st.session_state.get(CURRENT_FEATURE_KEY)

# Render selectbox (dropdown) for feature selection
# Build labels dynamically (these change with language, but values stay stable)
feature_labels = {f.feature_id: FeatureRegistry.get_instance(f.feature_id).display_label for f in features}

# Render selectbox with stable feature_ids as options
# No index parameter - let Streamlit manage state via key
selected_id = st.selectbox(
t("app.features"),
options=feature_ids,
Expand Down
3 changes: 2 additions & 1 deletion ui/features/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""

from features.auto_arena import AutoArenaFeature
from features.auto_rubric import AutoRubricFeature
from features.grader import GraderFeature

__all__ = ["GraderFeature", "AutoArenaFeature"]
__all__ = ["GraderFeature", "AutoArenaFeature", "AutoRubricFeature"]
14 changes: 10 additions & 4 deletions ui/features/auto_arena/components/config_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,24 +192,30 @@ def _render_single_endpoint(
)

with col2:
# Use stable value for custom option to survive UI language switch
CUSTOM_VALUE = "_custom_"

# Check if there's a custom model value already set (e.g., from preset)
current_model = st.session_state.get(f"arena_ep_model_{endpoint_id}", "")

# Build options list - include current model if it's custom
model_options = list(DEFAULT_MODELS)
custom_label = t("model.custom")
if current_model and current_model not in model_options and current_model != custom_label:
if current_model and current_model not in model_options and current_model != CUSTOM_VALUE:
model_options.insert(0, current_model)
model_options.append(custom_label)
model_options.append(CUSTOM_VALUE)

def format_model(x: str) -> str:
return t("model.custom") if x == CUSTOM_VALUE else x

model_option = st.selectbox(
t("model.select"),
options=model_options,
format_func=format_model,
key=f"arena_ep_model_{endpoint_id}",
)

# Row 1.5: Custom model input (only if "Custom..." selected)
if model_option == t("model.custom"):
if model_option == CUSTOM_VALUE:
model = st.text_input(
t("model.custom_input"),
placeholder=t("model.custom_placeholder"),
Expand Down
33 changes: 24 additions & 9 deletions ui/features/auto_arena/components/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ def _apply_preset_sidebar_data() -> None:

st.session_state["arena_judge_api_key"] = preset_data.get("judge_api_key", "")

# Judge model
# Judge model - use stable value for custom option
CUSTOM_VALUE = "_custom_"
judge_model = preset_data.get("judge_model", "")
if judge_model in DEFAULT_MODELS:
st.session_state["arena_judge_model"] = judge_model
st.session_state["arena_judge_model_value"] = judge_model
else:
st.session_state["arena_judge_model"] = "Custom..."
st.session_state["arena_judge_model_value"] = CUSTOM_VALUE
st.session_state["arena_judge_custom_model"] = judge_model

# Evaluation settings
Expand All @@ -59,10 +60,13 @@ def _render_judge_settings(config: dict[str, Any]) -> None:
"""Render judge model settings section."""
st.markdown(f'<div class="section-header">{t("arena.sidebar.judge_model")}</div>', unsafe_allow_html=True)

provider_options = list(DEFAULT_API_ENDPOINTS.keys())
if "arena_judge_provider" not in st.session_state:
st.session_state["arena_judge_provider"] = provider_options[0]

endpoint_choice = st.selectbox(
t("api.provider"),
options=list(DEFAULT_API_ENDPOINTS.keys()),
index=0,
options=provider_options,
help=t("arena.sidebar.judge_provider_help"),
key="arena_judge_provider",
)
Expand Down Expand Up @@ -90,14 +94,25 @@ def _render_judge_settings(config: dict[str, Any]) -> None:
else:
st.warning(t("api.key_required"))

# Use stable value for custom option to survive UI language switch
CUSTOM_VALUE = "_custom_"
model_options = DEFAULT_MODELS + [CUSTOM_VALUE]

def format_model_option(x: str) -> str:
return t("model.custom") if x == CUSTOM_VALUE else x

# Initialize default value in session state if not exists
if "arena_judge_model_value" not in st.session_state:
st.session_state["arena_judge_model_value"] = DEFAULT_MODELS[0] if DEFAULT_MODELS else CUSTOM_VALUE

model_option = st.selectbox(
t("model.select"),
options=DEFAULT_MODELS + [t("model.custom")],
index=0,
key="arena_judge_model",
options=model_options,
format_func=format_model_option,
key="arena_judge_model_value",
)

if model_option == t("model.custom"):
if model_option == CUSTOM_VALUE:
model_name = st.text_input(
t("model.custom_input"),
placeholder=t("model.custom_placeholder"),
Expand Down
15 changes: 15 additions & 0 deletions ui/features/auto_rubric/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
"""Auto Rubric feature module for OpenJudge Studio.

This feature provides automatic rubric generation from task descriptions
or labeled data, eliminating manual rubric design.

Phase 1 implements:
- Simple Rubric mode (zero-shot from task description)
- Basic result display
- Export functionality (Python/YAML/JSON)
"""

from features.auto_rubric.feature import AutoRubricFeature

__all__ = ["AutoRubricFeature"]
36 changes: 36 additions & 0 deletions ui/features/auto_rubric/components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
"""UI components for Auto Rubric feature."""

from features.auto_rubric.components.data_upload_panel import render_data_upload_panel
from features.auto_rubric.components.history_panel import (
render_history_panel,
render_task_detail,
)
from features.auto_rubric.components.iterative_config_panel import (
render_iterative_config_panel,
validate_iterative_config,
)
from features.auto_rubric.components.result_panel import render_result_panel
from features.auto_rubric.components.rubric_tester import (
render_test_panel,
render_test_section_compact,
)
from features.auto_rubric.components.sidebar import render_rubric_sidebar
from features.auto_rubric.components.simple_config_panel import (
render_simple_config_panel,
validate_simple_config,
)

__all__ = [
"render_rubric_sidebar",
"render_simple_config_panel",
"validate_simple_config",
"render_iterative_config_panel",
"validate_iterative_config",
"render_data_upload_panel",
"render_result_panel",
"render_history_panel",
"render_task_detail",
"render_test_panel",
"render_test_section_compact",
]
Loading