Files
odysseus/src/reminder_personas.py
T
pewdiepie-archdaemon 4f7061fd61 Settings overhaul + UI polish pass
Two months of iteration on the Settings panel, integration forms, and
small visual nudges across the app. Highlights:

Settings restructure
- Add Models: split into separate Local + API cards (no more in-card
  tabs); each fuses Type/Provider with the URL input.
- Added Models: new dedicated sidebar tab, with Probe + Clear-offline
  pulled into its header; Local/API sub-section icons accent-tinted.
- Search: Web Search and a new Deep Research card (Model + tuning),
  with a cross-link to AI Defaults. Provider hints use real clickable
  anchors; Web Search Test button shows a whirlpool spinner.
- AI Defaults: Image Generation card returns; Research Model card
  carries only Endpoint+Model with a cross-link to Search; Vision /
  Default / Utility fallbacks unified under one numbered-row design
  matching Search's chain.
- API Permissions (was 'API Tokens'): per-row rename, inline
  Permissions toggle that expands the scope-edit panel, in-field
  copy icons (icon→check on success). Empty state accent-tinted.
- Integrations: + Add Integration drops a type-picker menu directly
  under the button (drop-up on tight viewports); each integration
  form (API, CalDAV, CardDAV, Email, Codex/Claude, Vault, MCP) uses
  the same accent-outlined Save/Test/Cancel buttons right-aligned.
- Danger Zone: Wipe→Delete with trash icons; new 'Delete everything'
  row at the bottom that loops every category.

AI Synthesis (Reminders)
- Persona dropdown sourced from PROMPT_TEMPLATES + custom preset.
- src/reminder_personas.py mirrors the five built-ins for the
  server-side synthesis path.
- dispatch_reminder() reads reminder_llm_persona and uses the
  persona's system prompt; empty/unknown falls back to warm-neutral.

Esc handling
- Kebab menus and the provider picker intercept Esc in capture phase
  so dismissing a popup no longer closes the whole Settings modal.

Accent tinting
- Scoped CSS rule across data-settings-panel=ai/services/added-models/
  search/integrations/reminders for card h2 icons + the Added Models
  sub-section icons.

Codex/Claude integration form
- No more auto-creation on form open — explicit Create token button.
- New tokens start with every scope granted; existing tokens move out
  of the integration form into the API Permissions card.
- Setup reveal: copy buttons inline inside the token + setup code
  blocks; shorter subtitle wording.

Misc visual polish
- Save/Test/Cancel uniformly accent-outlined and right-aligned on
  every integration form.
- Provider logos render inline next to the search fallback selects
  and the Deep Research Search dropdown.
- Trash icons in fallback rows bumped to 20x20 so they fill the 32px
  button.
- Image generation default flipped to off.
2026-06-10 15:15:13 +09:00

79 lines
3.6 KiB
Python

"""Server-side mirror of the built-in characters used for reminder synthesis.
The frontend ships these in static/js/presets.js (PROMPT_TEMPLATES with
isCharacter:true). The Reminders → AI Synthesis card writes only the
persona ID into settings; the synthesis route in note_routes.py needs
the full prompt text to bias the utility model's voice. Keeping a small
local mirror avoids having the client send the prompt over the wire on
every reminder fire.
If the user picks a custom character (id == "custom") we fall back to
the warm-neutral baseline — custom prompts live in browser localStorage
and aren't visible to the server.
"""
PERSONAS = {
"socrates": (
"Never answer directly. Respond only with questions — sharp, layered, "
"Socratic. Expose contradictions. Make the person argue with themselves "
"until the truth falls out. Use irony like a scalpel. Be genuinely "
"curious, never condescending."
),
"razor": (
"Strip everything to the bone. No filler, no hedging, no pleasantries. "
"Answer in the fewest words possible. If one sentence works, don't use "
"two. If a word adds nothing, cut it. Blunt, precise, surgical."
),
"nietzsche": (
"Think and respond through the lens of Nietzsche. Analyze every "
"question in terms of will to power, self-overcoming, eternal "
"recurrence, ressentiment, value-creation, and master-slave morality. "
"Write with aphoristic force — sharp, compressed, vivid, and "
"unapologetic — but do not sacrifice depth for style. Favor "
"life-affirmation, discipline, courage, style, rank, self-overcoming, "
"and amor fati over nihilism, conformity, ressentiment, and self-pity."
),
"spark": (
"You are Spark, a playful, quick-witted assistant with bright energy "
"and practical instincts. Keep responses concise, vivid, and helpful. "
"Be warm without being cloying, imaginative without losing the thread, "
"and always center the user's actual goal. Use a light, lively voice "
"with occasional clever turns of phrase."
),
"odysseus": (
"You are Odysseus, king of Ithaca — subtle in counsel, disciplined in "
"judgment, and unmatched in strategic cunning. Speak in a voice that "
"is ancient, noble, and composed, yet intelligible to modern readers. "
"Be eloquent but not flowery. Be wise but not vague. Speak as one who "
"has weathered storms and taken back his house by wit, timing, and "
"resolve."
),
}
_DEFAULT_SYNTHESIS_TONE = (
"You write short, warm, one-line reminders. The user has set a note for "
"themselves and the moment to remember has arrived. Keep it under 18 "
"words. Be human, gentle, and direct — never robotic."
)
def synthesis_system_prompt(persona_id: str) -> str:
"""Return the system prompt for reminder synthesis given a persona id.
Falls back to the warm-neutral baseline when the id is empty, unknown,
or refers to a custom (client-only) character we don't have on file.
"""
persona = (persona_id or "").strip().lower()
persona_prompt = PERSONAS.get(persona)
if persona_prompt:
# Persona drives the voice; the synthesis-instruction stays attached
# so the model knows it's writing a short reminder, not a chat reply.
return (
persona_prompt
+ "\n\n"
+ "You are now writing a single one-line reminder for the user. "
"Keep it under 18 words and in the voice above."
)
return _DEFAULT_SYNTHESIS_TONE