feat(studio): cached/Regenerate UX in audit Explain (v0.16.0)

Audit drilldown's explanation panel now surfaces whether the
answer came from the cache:
- "cached" pill next to the SYSTEM AI label, tooltip shows
  the original generation date + hit count.
- Latency renders as "orig 47000 ms" so the operator sees
  what a live call would have cost.
- Refresh icon next to the latency triggers Regenerate —
  drops the entry, re-asks the model.

System-AI editor shows a cache-status row when the cache is
non-empty with a one-click Clear button. Hidden on fresh
installs to keep the dialog quiet.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-07 17:29:36 +02:00
parent b32b4b5bda
commit d82738e17e
6 changed files with 161 additions and 9 deletions

View file

@ -240,6 +240,27 @@ class _FaiSystemAiEditorState extends State<FaiSystemAiEditor> {
}
}
Future<void> _clearCache() async {
try {
final purged = await HubService.instance.clearSystemLlmCache();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Cleared $purged cached System-AI explanations.')),
);
// Pop with the same status (since the editor's `initial`
// is now stale w.r.t. cacheCount); caller will re-fetch
// status on next open.
setState(() {
// Force a rebuild that hides the row; cheap.
});
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Cache clear failed: $e')),
);
}
}
Future<void> _test() async {
setState(() {
_testing = true;
@ -420,6 +441,13 @@ class _FaiSystemAiEditorState extends State<FaiSystemAiEditor> {
value: _privacyMode,
onChanged: (v) => setState(() => _privacyMode = v),
),
if (widget.initial.cacheCount > 0) ...[
const SizedBox(height: FaiSpace.md),
_CacheStatusRow(
cacheCount: widget.initial.cacheCount,
onClear: _saving || _testing ? null : _clearCache,
),
],
if (_error != null) ...[
const SizedBox(height: FaiSpace.md),
Text(
@ -1083,3 +1111,56 @@ class _SuitabilityLegend extends StatelessWidget {
);
}
}
/// Displays the System-AI cache count plus a "Clear cache"
/// button. Hidden when the cache is empty so the dialog stays
/// quiet for fresh installs. Lives inline so the operator sees
/// the cache exists, what it costs, and how to drop it without
/// reading docs.
class _CacheStatusRow extends StatelessWidget {
final int cacheCount;
final VoidCallback? onClear;
const _CacheStatusRow({required this.cacheCount, required this.onClear});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.symmetric(
horizontal: FaiSpace.sm,
vertical: 6,
),
decoration: BoxDecoration(
color: theme.colorScheme.surfaceContainerHigh,
borderRadius: BorderRadius.circular(FaiRadius.sm),
border: Border.all(color: theme.colorScheme.outlineVariant),
),
child: Row(
children: [
Icon(
Icons.bolt,
size: 14,
color: theme.colorScheme.onSurfaceVariant,
),
const SizedBox(width: 6),
Expanded(
child: Text(
'Cache: $cacheCount cached explanation${cacheCount == 1 ? "" : "s"}. '
'Identical prompts hit the cache; switching model or privacy '
'mode flushes automatically.',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
TextButton.icon(
onPressed: onClear,
icon: const Icon(Icons.delete_outline, size: 14),
label: const Text('Clear'),
),
],
),
);
}
}