feat: add settings to chose timzone and add markdown readability on main page
This commit is contained in:
@ -126,17 +126,28 @@ function ContextPanel({
|
||||
|
||||
// ── Summary content renderer ────────────────────────────────────────────────
|
||||
|
||||
function renderInline(text: string): React.ReactNode {
|
||||
const parts = text.split(/(\*\*[^*]+\*\*)/g)
|
||||
return parts.map((part, i) =>
|
||||
part.startsWith('**') && part.endsWith('**')
|
||||
? <strong key={i}>{part.slice(2, -2)}</strong>
|
||||
: part
|
||||
)
|
||||
}
|
||||
|
||||
function SummaryContent({ content }: { content: string }) {
|
||||
const lines = content.split('\n')
|
||||
return (
|
||||
<div className="space-y-2 text-sm leading-relaxed select-text">
|
||||
<div className="space-y-1 text-sm leading-relaxed select-text">
|
||||
{lines.map((line, i) => {
|
||||
if (line.startsWith('## ')) return <h2 key={i} className="text-base font-semibold mt-4 first:mt-0">{line.slice(3)}</h2>
|
||||
if (line.startsWith('### ')) return <h3 key={i} className="font-medium mt-3">{line.slice(4)}</h3>
|
||||
if (line.startsWith('- ')) return <li key={i} className="ml-4 text-muted-foreground">{line.slice(2)}</li>
|
||||
if (line.startsWith('**') && line.endsWith('**')) return <p key={i} className="font-semibold">{line.slice(2, -2)}</p>
|
||||
if (line.trim() === '') return <div key={i} className="h-1" />
|
||||
return <p key={i} className="text-muted-foreground">{line}</p>
|
||||
if (line.startsWith('##### ')) return <h5 key={i} className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mt-3">{renderInline(line.slice(6))}</h5>
|
||||
if (line.startsWith('#### ')) return <h4 key={i} className="text-sm font-semibold mt-3">{renderInline(line.slice(5))}</h4>
|
||||
if (line.startsWith('### ')) return <h3 key={i} className="text-sm font-semibold text-primary mt-4">{renderInline(line.slice(4))}</h3>
|
||||
if (line.startsWith('## ')) return <h2 key={i} className="text-base font-bold mt-5 first:mt-0 border-b pb-1">{renderInline(line.slice(3))}</h2>
|
||||
if (line.startsWith('# ')) return <h1 key={i} className="text-lg font-bold mt-5 first:mt-0">{renderInline(line.slice(2))}</h1>
|
||||
if (line.startsWith('- ') || line.startsWith('* ')) return <li key={i} className="ml-4 text-muted-foreground list-disc">{renderInline(line.slice(2))}</li>
|
||||
if (line.trim() === '') return <div key={i} className="h-2" />
|
||||
return <p key={i} className="text-muted-foreground">{renderInline(line)}</p>
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -8,19 +8,26 @@ import { Label } from '@/components/ui/label'
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
|
||||
const NUMERIC_SETTINGS: Record<string, { label: string; description: string }> = {
|
||||
scrape_interval_minutes: { label: 'Intervalle de scraping (minutes)', description: 'Fréquence de récupération des actualités' },
|
||||
articles_lookback_hours: { label: 'Fenêtre d\'analyse (heures)', description: 'Période couverte pour les résumés IA' },
|
||||
summary_max_articles: { label: 'Articles max par résumé', description: 'Nombre maximum d\'articles envoyés à l\'IA' },
|
||||
}
|
||||
|
||||
const COMMON_TIMEZONES = [
|
||||
'Europe/Paris', 'Europe/London', 'Europe/Berlin', 'Europe/Madrid', 'Europe/Rome',
|
||||
'America/New_York', 'America/Chicago', 'America/Los_Angeles',
|
||||
'Asia/Tokyo', 'Asia/Hong_Kong', 'Asia/Singapore',
|
||||
'UTC',
|
||||
]
|
||||
|
||||
export function AdminSettings() {
|
||||
const [settings, setSettings] = useState<Setting[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [values, setValues] = useState<Record<string, string>>({})
|
||||
const [defaultPrompt, setDefaultPrompt] = useState('')
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [savingTz, setSavingTz] = useState(false)
|
||||
const [savingPrompt, setSavingPrompt] = useState(false)
|
||||
const [saved, setSaved] = useState(false)
|
||||
const [savedTz, setSavedTz] = useState(false)
|
||||
const [savedPrompt, setSavedPrompt] = useState(false)
|
||||
|
||||
useEffect(() => { load() }, [])
|
||||
@ -53,6 +60,13 @@ export function AdminSettings() {
|
||||
setTimeout(() => setSaved(false), 2000)
|
||||
}
|
||||
|
||||
async function saveTimezone() {
|
||||
setSavingTz(true); setSavedTz(false)
|
||||
await adminApi.updateSettings([{ key: 'timezone', value: values['timezone'] ?? 'Europe/Paris' }])
|
||||
setSavingTz(false); setSavedTz(true)
|
||||
setTimeout(() => setSavedTz(false), 2000)
|
||||
}
|
||||
|
||||
async function savePrompt() {
|
||||
setSavingPrompt(true); setSavedPrompt(false)
|
||||
await adminApi.updateSettings([{ key: 'ai_system_prompt', value: values['ai_system_prompt'] ?? '' }])
|
||||
@ -75,6 +89,35 @@ export function AdminSettings() {
|
||||
<p className="text-muted-foreground text-sm">Configuration globale du service</p>
|
||||
</div>
|
||||
|
||||
{/* Fuseau horaire */}
|
||||
<Card>
|
||||
<CardHeader><CardTitle>Fuseau horaire</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<Label>Timezone (IANA)</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Utilisé pour le planning de scraping et l'horodatage des résumés
|
||||
</p>
|
||||
<div className="flex gap-2 max-w-sm">
|
||||
<Input
|
||||
list="tz-list"
|
||||
value={values['timezone'] ?? 'Europe/Paris'}
|
||||
onChange={e => setValues(v => ({ ...v, timezone: e.target.value }))}
|
||||
placeholder="Europe/Paris"
|
||||
/>
|
||||
<datalist id="tz-list">
|
||||
{COMMON_TIMEZONES.map(tz => <option key={tz} value={tz} />)}
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={saveTimezone} disabled={savingTz}>
|
||||
{savingTz ? <Spinner className="h-4 w-4" /> : <Save className="h-4 w-4" />}
|
||||
{savedTz ? 'Enregistré !' : 'Enregistrer'}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Paramètres généraux */}
|
||||
<Card>
|
||||
<CardHeader><CardTitle>Paramètres généraux</CardTitle></CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
@ -101,6 +144,7 @@ export function AdminSettings() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Contexte IA */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Contexte IA</CardTitle>
|
||||
|
||||
Reference in New Issue
Block a user