Add insight history view; feed recent insights back into the prompt

Dashboard now shows a collapsible history of past AI insights (reusing
the existing /history endpoint), and generation now includes the last
3 insights in the prompt so the model can reference what it already
said instead of repeating the same points every day.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-24 22:01:13 +00:00
parent cabaff8d35
commit 6339b33270
4 changed files with 100 additions and 4 deletions

View file

@ -94,6 +94,10 @@ export function getLatestInsight(): Promise<AIInsight | null> {
return request('/ai-insights/latest')
}
export function getInsightHistory(limit = 20, offset = 0): Promise<{ insights: AIInsight[]; total: number }> {
return request(`/ai-insights/history?limit=${limit}&offset=${offset}`)
}
export function generateInsight(): Promise<{ success: boolean; content: string; input_tokens: number; output_tokens: number; model: string }> {
return request('/ai-insights/generate', { method: 'POST' })
}

View file

@ -0,0 +1,64 @@
import { useState, useEffect } from 'react'
import { History, ChevronDown, ChevronRight, Clock } from 'lucide-react'
import { getInsightHistory } from '../api'
import { formatAge, renderContent } from '../lib/aiInsight'
import type { AIInsight } from '../types'
export default function AIInsightHistory({ refreshKey }: { refreshKey?: number }) {
const [insights, setInsights] = useState<AIInsight[]>([])
const [loading, setLoading] = useState(true)
const [expandedId, setExpandedId] = useState<number | null>(null)
useEffect(() => {
setLoading(true)
getInsightHistory(20)
.then(res => setInsights(res.insights))
.finally(() => setLoading(false))
}, [refreshKey])
return (
<div className="card" style={{ marginTop: 16 }}>
<div className="card-title" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<History size={16} strokeWidth={1.75} color="var(--gold)" />
Insight History
</div>
{loading && <div className="state-center">Loading history</div>}
{!loading && insights.length === 0 && <div className="state-center">No past insights yet.</div>}
{insights.map((item, i) => {
const isOpen = expandedId === item.id
return (
<div key={item.id} style={{ borderTop: i === 0 ? 'none' : '1px solid var(--border)' }}>
<button
onClick={() => setExpandedId(isOpen ? null : item.id)}
style={{
display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '10px 0',
background: 'none', border: 'none', cursor: 'pointer', textAlign: 'left', font: 'inherit',
}}
>
{isOpen
? <ChevronDown size={14} strokeWidth={1.75} color="var(--text-muted)" style={{ flexShrink: 0 }} />
: <ChevronRight size={14} strokeWidth={1.75} color="var(--text-muted)" style={{ flexShrink: 0 }} />}
<span style={{ flex: 1, fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>
{new Date(item.generated_at).toLocaleString('en-GB', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })}
</span>
<span style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 11, color: 'var(--text-muted)', flexShrink: 0 }}>
<Clock size={11} strokeWidth={1.75} />
{formatAge(item.generated_at)}
</span>
</button>
{isOpen && (
<div style={{ padding: '0 0 16px 24px', fontSize: 13.5, lineHeight: 1.65, color: 'var(--text-primary)' }}>
{renderContent(item.content)}
<div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 12 }}>
{item.input_tokens} / {item.output_tokens} tokens · {item.triggered_by}
</div>
</div>
)}
</div>
)
})}
</div>
)
}

View file

@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'
import { Bot, Clock, RefreshCw } from 'lucide-react'
import { getLatestInsight, generateInsight } from '../api'
import { formatAge, renderContent } from '../lib/aiInsight'
import AIInsightHistory from '../components/AIInsightHistory'
import type { AIInsight } from '../types'
const REFRESH_MS = 5 * 60_000
@ -12,6 +13,7 @@ export default function Dashboard() {
const [error, setError] = useState<string | null>(null)
const [generating, setGenerating] = useState(false)
const [genError, setGenError] = useState<string | null>(null)
const [historyKey, setHistoryKey] = useState(0)
const load = useCallback(() => {
getLatestInsight()
@ -32,6 +34,7 @@ export default function Dashboard() {
try {
await generateInsight()
load()
setHistoryKey(k => k + 1)
} catch (e) {
setGenError(e instanceof Error ? e.message : 'Failed to generate insight')
} finally {
@ -88,6 +91,8 @@ export default function Dashboard() {
</div>
)}
</div>
<AIInsightHistory refreshKey={historyKey} />
</div>
)
}