"""Editing the shipped system prompts. Every prompt has a code default; a row exists only where an admin changed one, and resetting deletes that row rather than storing a copy of the default. The UI labels the keys from its own messages, so nothing user-facing is worded here. """ from typing import Annotated from fastapi import Depends from pydantic import BaseModel from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.api.admin.routing import admin_router from app.db import get_db from app.errors import ApiError from app.models import PromptSetting from app.prompts.defaults import DEFAULTS, PROMPT_KEYS from app.prompts.overrides import get_prompt, is_overridden from app.prompts.overrides import load_config as load_prompt_config router = admin_router() class PromptSettingOut(BaseModel): key: str # The effective text: an admin override if present, else the code default. content: str # True when no override exists, i.e. the shipped default is in force. is_default: bool class PromptSettingUpdate(BaseModel): # New override text, or `reset` to drop the override back to the default. content: str | None = None reset: bool | None = None @router.get("/prompts") async def prompt_settings( db: Annotated[AsyncSession, Depends(get_db)], ) -> list[PromptSettingOut]: """Every editable system prompt with its effective text and whether it is still the shipped default.""" rows = {row.key: row for row in (await db.execute(select(PromptSetting))).scalars()} return [ PromptSettingOut( key=key, content=rows[key].content if key in rows else DEFAULTS[key], is_default=key not in rows, ) for key in PROMPT_KEYS ] @router.put("/prompts/{key}") async def update_prompt_setting( key: str, body: PromptSettingUpdate, db: Annotated[AsyncSession, Depends(get_db)], ) -> PromptSettingOut: """Override a system prompt (applied without a restart) or reset it to the shipped default. Resetting deletes the override row.""" if key not in DEFAULTS: raise ApiError(404, "Unknown prompt.", "not_found") row = ( await db.execute(select(PromptSetting).where(PromptSetting.key == key)) ).scalar_one_or_none() if body.reset: if row is not None: await db.delete(row) elif body.content is not None: content = body.content.strip() if not content: raise ApiError(422, "A prompt cannot be empty.", "empty_prompt") if row is None: db.add(PromptSetting(key=key, content=content)) else: row.content = content await db.commit() await load_prompt_config(db) return PromptSettingOut( key=key, content=get_prompt(key), is_default=not is_overridden(key) )