Make the AI insight's prompt instructions editable via Settings
Split the system prompt into an editable part (persona/focus/tone/ format — what the briefing prioritises and how strict its length is) and a fixed structural appendix (how the Manual Context and Recent Previous Insights sections get interpreted, which map directly to conditional data the code assembles, not just prose — kept safe from being accidentally edited away). New ai_insights_prompt_instructions config key, a GET /api/ai-insights/default-prompt endpoint so the Settings UI can pre-fill/reset to the built-in default, and a textarea in the AI Insights settings card. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
8f8e273650
commit
e447d48e37
6 changed files with 73 additions and 23 deletions
|
|
@ -105,3 +105,7 @@ export function generateInsight(): Promise<{ success: boolean; content: string;
|
|||
export function testAiInsightsConnection(): Promise<{ status: string; message: string }> {
|
||||
return request('/ai-insights/test', { method: 'POST' })
|
||||
}
|
||||
|
||||
export function getDefaultPrompt(): Promise<{ prompt: string }> {
|
||||
return request('/ai-insights/default-prompt')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { RefreshCw, Download, X, CheckSquare, Square, Bot } from 'lucide-react'
|
||||
import { getSettings, saveSettings, getDepartments, triggerSync, getSyncStatus, cancelBackfill, cancelRotaBackfill, testAiInsightsConnection, generateInsight } from '../api'
|
||||
import { getSettings, saveSettings, getDepartments, triggerSync, getSyncStatus, cancelBackfill, cancelRotaBackfill, testAiInsightsConnection, generateInsight, getDefaultPrompt } from '../api'
|
||||
import type { AppSetting, Department } from '../types'
|
||||
|
||||
function fmtDate(iso: string | null): string {
|
||||
|
|
@ -26,14 +26,16 @@ export default function SettingsPage() {
|
|||
const [testMessage, setTestMessage] = useState('')
|
||||
const [genStatus, setGenStatus] = useState<'idle' | 'generating' | 'done' | 'error'>('idle')
|
||||
const [genMessage, setGenMessage] = useState('')
|
||||
const [defaultPrompt, setDefaultPrompt] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([getSettings(), getSyncStatus()])
|
||||
.then(([settRes, statusRes]) => {
|
||||
Promise.all([getSettings(), getSyncStatus(), getDefaultPrompt()])
|
||||
.then(([settRes, statusRes, promptRes]) => {
|
||||
const map: Record<string, string> = {}
|
||||
for (const s of settRes.settings as AppSetting[]) map[s.key] = s.value
|
||||
setSettings(map)
|
||||
setSyncStatus(statusRes)
|
||||
setDefaultPrompt(promptRes.prompt)
|
||||
|
||||
// Parse saved departments if present
|
||||
if (map.departments) {
|
||||
|
|
@ -63,6 +65,7 @@ export default function SettingsPage() {
|
|||
{ key: 'ai_insights_schedule_time', value: settings.ai_insights_schedule_time ?? '07:15' },
|
||||
{ key: 'ai_insights_daily_token_budget', value: settings.ai_insights_daily_token_budget ?? '5000' },
|
||||
{ key: 'ai_insights_manual_context', value: settings.ai_insights_manual_context ?? '' },
|
||||
{ key: 'ai_insights_prompt_instructions', value: settings.ai_insights_prompt_instructions ?? '' },
|
||||
])
|
||||
setSaved(true)
|
||||
setTimeout(() => setSaved(false), 2000)
|
||||
|
|
@ -362,6 +365,33 @@ export default function SettingsPage() {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
|
||||
<label style={{ fontSize: 12, fontWeight: 500, color: 'var(--text-muted)' }}>
|
||||
Prompt Instructions
|
||||
</label>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
style={{ fontSize: 11, padding: '2px 8px' }}
|
||||
onClick={() => handleChange('ai_insights_prompt_instructions', defaultPrompt)}
|
||||
>
|
||||
Reset to default
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
rows={8}
|
||||
style={{ width: '100%', resize: 'vertical', fontFamily: 'inherit', fontSize: 12.5 }}
|
||||
value={settings.ai_insights_prompt_instructions || defaultPrompt}
|
||||
onChange={e => handleChange('ai_insights_prompt_instructions', e.target.value)}
|
||||
/>
|
||||
<p style={{ margin: '4px 0 0', fontSize: 12, color: 'var(--text-muted)' }}>
|
||||
The persona/focus/tone/format instructions given to the model — edit freely to change what it
|
||||
prioritises, how strict the length limit is, or its tone. Pre-filled with the built-in default;
|
||||
how the Manual Context and Recent Previous Insights sections above get interpreted is always
|
||||
appended automatically and can't be removed here.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<button className="btn btn-secondary" onClick={handleTestConnection} disabled={testStatus === 'testing'}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue