Tasks: optional persona for LLM + research tasks (biases output voice)

Wire the existing built-in PERSONAS catalog through to scheduled tasks
the same way I wired it to reminder synthesis. Repurposes the
dormant scheduled_tasks.character_id column.

UI (static/js/tasks.js)
- New 'Persona' select in the LLM / Research task form, with the five
  built-in characters (socrates/razor/nietzsche/spark/odysseus) plus a
  default 'no persona' option. Pre-populates from existing.character_id
  on edit. Non-llm/research types explicitly clear it on save.

API (routes/task_routes.py)
- TaskCreate + TaskUpdate gain character_id: Optional[str].
- _task_to_dict echoes character_id back so the form can hydrate on
  edit. Update endpoint stores '' as None to allow clearing.

Runner (src/task_scheduler.py)
- When task.character_id is set and matches a built-in persona, prepend
  the persona prompt to the task system prompt so the model speaks in
  that voice while still knowing it's running a scheduled task.
- crew_member.personality still wins as the base; character_id stacks
  on top.
This commit is contained in:
pewdiepie-archdaemon
2026-06-10 23:36:18 +09:00
parent a86990fc58
commit 2bf372b41c
3 changed files with 38 additions and 0 deletions
+18
View File
@@ -1077,9 +1077,23 @@ function _showForm(existing, initTaskType, initTriggerType) {
typeOpts.innerHTML = '';
if (taskType === 'llm' || taskType === 'research') {
const placeholder = taskType === 'research' ? 'What should be researched?' : 'What should the AI do?';
const _personaOpts = [
['', 'Default (no persona)'],
['socrates', 'Socrates'],
['razor', 'Razor'],
['nietzsche', 'Nietzsche'],
['spark', 'Spark'],
['odysseus', 'Odysseus'],
];
const _curPersona = (existing?.character_id || '').toLowerCase();
const _personaOptsHtml = _personaOpts.map(([v, label]) =>
`<option value="${v}" ${v === _curPersona ? 'selected' : ''}>${label}</option>`).join('');
typeOpts.innerHTML = `
<label class="task-form-label">${taskType === 'research' ? 'Research question' : 'Prompt'}</label>
<textarea id="task-form-prompt" class="task-form-input task-form-textarea" rows="4" placeholder="${placeholder}">${existing?.prompt || ''}</textarea>
<label class="task-form-label">Persona <span style="opacity:0.5;font-weight:normal;font-size:10px;">(optional — biases the output voice)</span></label>
<select id="task-form-persona" class="task-form-input">${_personaOptsHtml}</select>
`;
} else {
typeOpts.innerHTML = `
@@ -1437,7 +1451,11 @@ function _showForm(existing, initTaskType, initTriggerType) {
return;
}
payload.prompt = prompt;
const personaVal = document.getElementById('task-form-persona')?.value || '';
payload.character_id = personaVal;
} else {
// Non-llm/research tasks: explicitly clear any persona on switch.
payload.character_id = '';
const action = document.getElementById('task-form-action')?.value;
if (!action) {
if (uiModule) uiModule.showError('Select an action');