Source code for pipeworks_mud_mapper.callbacks.ollama_template_callbacks

"""Callbacks for Ollama template, prompt, and control UI.

This module handles template dropdown population, system prompt compilation,
prompt prefix presets, collapse toggles, and seed control logic.
"""

import time
from typing import Any

from dash import Input, Output, State, callback, ctx, no_update

from pipeworks_mud_mapper.services import template_service
from pipeworks_mud_mapper.services.ollama_assets import (
    load_parameter_presets,
    load_prompt_prefixes,
)
from pipeworks_mud_mapper.services.ollama_config import DEFAULT_TARGET_WORDS
from pipeworks_mud_mapper.services.ollama_ui import status_ok, status_warning


def _ollama_status_payload(content: Any) -> dict[str, Any]:
    """Build a timestamped status payload for the status renderer."""
    return {"content": content, "ts": time.monotonic()}


# =============================================================================
# Template Callbacks
# =============================================================================


[docs] @callback( Output("ollama-template-dropdown", "options"), Input("ollama-refresh-models-btn", "n_clicks"), # Refresh templates when models refresh prevent_initial_call=False, # Load templates on app startup ) def load_template_options(n_clicks: int) -> list[dict]: """Load available templates for the dropdown. Called on app startup and when the refresh button is clicked. Templates are loaded from ``data/ollama/templates/``. """ # Get templates from service templates = template_service.list_templates() return templates
[docs] @callback( Output("ollama-prompt-prefix-dropdown", "options"), Input("ollama-refresh-models-btn", "n_clicks"), prevent_initial_call=False, ) def load_prompt_prefix_options(n_clicks: int) -> list[dict]: """Load prompt prefix presets from JSON config. We reload on refresh to allow authors to edit the JSON and refresh without restarting the app. """ prefix_data = load_prompt_prefixes(reload=True) options = [ {"label": item.get("label", item.get("value")), "value": item.get("value")} for item in prefix_data if isinstance(item, dict) ] return options
[docs] @callback( Output("ollama-params-preset-dropdown", "options"), Input("ollama-refresh-models-btn", "n_clicks"), prevent_initial_call=False, ) def load_param_preset_options(n_clicks: int) -> list[dict]: """Load parameter presets from JSON files.""" # Reload so authors can drop a new preset file in the directory # and immediately see it after hitting refresh. preset_data = load_parameter_presets(reload=True) return [ {"label": item.get("label", item.get("value")), "value": item.get("value")} for item in preset_data if isinstance(item, dict) ]
[docs] @callback( Output("ollama-target-words", "value"), Output("ollama-temperature", "value"), Output("ollama-top-p", "value"), Output("ollama-top-k", "value"), Output("ollama-num-predict", "value"), Input("ollama-params-preset-apply", "n_clicks"), State("ollama-params-preset-dropdown", "value"), State("ollama-target-words", "value"), State("ollama-temperature", "value"), State("ollama-top-p", "value"), State("ollama-top-k", "value"), State("ollama-num-predict", "value"), prevent_initial_call=True, ) def apply_param_preset( n_clicks: int, preset_id: str | None, target_words: int | None, temperature: float | None, top_p: float | None, top_k: int | None, num_predict: int | None, ) -> tuple: """Apply a parameter preset to the tuning controls.""" if not n_clicks or not preset_id: return no_update, no_update, no_update, no_update, no_update # Reload on apply to avoid stale files during iterative authoring. preset_data = load_parameter_presets(reload=True) preset = next( (item for item in preset_data if isinstance(item, dict) and item.get("value") == preset_id), None, ) if not preset: return no_update, no_update, no_update, no_update, no_update return ( # Each field falls back to the current value if the preset omits it, # keeping presets lightweight and allowing partial overrides. int(preset.get("target_words", target_words or DEFAULT_TARGET_WORDS)), float(preset.get("temperature", temperature if temperature is not None else 0.7)), float(preset.get("top_p", top_p if top_p is not None else 0.9)), int(preset.get("top_k", top_k if top_k is not None else 40)), int(preset.get("num_predict", num_predict if num_predict is not None else 140)), )
[docs] @callback( Output("ollama-user-prompt", "value", allow_duplicate=True), Input("ollama-prompt-prefix-dropdown", "value"), State("ollama-user-prompt", "value"), prevent_initial_call=True, ) def apply_prompt_prefix(prefix_id: str | None, current_prompt: str | None) -> Any: """Prepend a selected prompt prefix to the user prompt.""" if not prefix_id: return no_update # Reload to avoid stale data if the file was edited during runtime. prefix_data = load_prompt_prefixes(reload=True) prefix = None for item in prefix_data: if isinstance(item, dict) and item.get("value") == prefix_id: prefix = item.get("prefix") break if not prefix: return no_update current_prompt = current_prompt or "" if current_prompt.strip().startswith(prefix): return current_prompt if current_prompt.strip(): return f"{prefix}\n{current_prompt.strip()}" return prefix
[docs] @callback( Output("ollama-system-prompt", "value"), Output("ollama-system-prompt", "readOnly"), Output("ollama-system-prompt-collapse", "is_open"), Output("ollama-system-prompt-chevron", "className"), Output("ollama-status-template", "data"), Input("ollama-template-dropdown", "value"), Input("ollama-target-words", "value"), prevent_initial_call=True, ) def handle_template_selection(template_id: str | None, target_words: int | None) -> tuple: """Handle template selection and target word count changes. When a template is selected or target words change, compiles the template into a system prompt with the specified word count guidance and updates the display. The system prompt remains read-only. """ # Apply default if target_words is None if target_words is None: target_words = DEFAULT_TARGET_WORDS # No selection made - don't update anything if not template_id: return no_update, no_update, no_update, no_update, no_update # Load the selected template from data/ollama/templates/ template = template_service.load_template(template_id) if not template: status = status_warning(f"Template '{template_id}' not found") # Don't update the prompt on error - keep whatever was there return no_update, no_update, no_update, no_update, _ollama_status_payload(status) # Compile the template JSON into a comprehensive system prompt string system_prompt = template_service.compile_system_prompt(template, target_words=target_words) status = status_ok(f"Loaded: {template.template_name} v{template.version}") # Return: compiled prompt, read-only (not editable), collapse CLOSED by default, # chevron right (closed state), status return ( system_prompt, True, False, "bi bi-chevron-right me-1", _ollama_status_payload(status), )
[docs] @callback( Output("ollama-target-words-hint", "children"), Input("ollama-target-words", "value"), ) def update_target_words_hint(target_words: int | None) -> str: """Update helper text to show the effective word target behavior.""" if not target_words: return "25-500: Guides LLM output length" if target_words <= 40: return f"Exact length: {target_words} words" words_low = int(target_words * 0.67) words_high = int(target_words * 1.17) return f"Range: {words_low}-{words_high} (aim ~{target_words})"
# ============================================================================= # UI Toggle and Seed Control Callbacks # =============================================================================
[docs] @callback( Output("ollama-system-prompt-collapse", "is_open", allow_duplicate=True), Output("ollama-system-prompt-chevron", "className", allow_duplicate=True), Input("ollama-system-prompt-toggle", "n_clicks"), State("ollama-system-prompt-collapse", "is_open"), prevent_initial_call=True, ) def toggle_system_prompt_collapse(n_clicks: int, is_open: bool) -> tuple: """Toggle the system prompt collapse open/closed.""" if not n_clicks: return no_update, no_update new_is_open = not is_open # Chevron down when open, right when closed icon_class = "bi bi-chevron-down me-1" if new_is_open else "bi bi-chevron-right me-1" return new_is_open, icon_class
[docs] @callback( Output("ollama-params-collapse", "is_open"), Output("ollama-params-chevron", "className"), Input("ollama-params-toggle", "n_clicks"), State("ollama-params-collapse", "is_open"), prevent_initial_call=True, ) def toggle_params_collapse(n_clicks: int, is_open: bool) -> tuple: """Toggle the parameters section collapse open/closed.""" if not n_clicks: return no_update, no_update new_is_open = not is_open # Chevron down when open, right when closed icon_class = "bi bi-chevron-down me-1" if new_is_open else "bi bi-chevron-right me-1" return new_is_open, icon_class
[docs] @callback( Output("ollama-seed-value", "value"), Output("ollama-seed-random-check", "value"), Input("ollama-seed-decrease", "n_clicks"), Input("ollama-seed-increase", "n_clicks"), Input("ollama-seed-random-check", "value"), State("ollama-seed-value", "value"), prevent_initial_call=True, ) def handle_seed_controls( decrease_clicks: int, increase_clicks: int, random_checked: bool, current_seed: int, ) -> tuple: """Handle seed control interactions (buttons and checkbox).""" # Determine which input triggered the callback triggered_id = ctx.triggered_id # Handle random checkbox changes if triggered_id == "ollama-seed-random-check": if random_checked: # Random mode enabled: set seed to -1 return -1, True # Random mode disabled: set seed to 0 (or keep current if valid) if current_seed == -1: return 0, False return current_seed, False # Handle +/- button clicks (only when not in random mode) if current_seed == -1: # In random mode, buttons don't change the seed return -1, True # Ensure current_seed is an integer seed = int(current_seed) if current_seed is not None else 0 if triggered_id == "ollama-seed-decrease": # Decrement seed, minimum is 0 (not -1, which means random) new_seed = max(0, seed - 1) return new_seed, False if triggered_id == "ollama-seed-increase": # Increment seed, no upper limit but keep it reasonable new_seed = seed + 1 return new_seed, False # Default: no change return no_update, no_update