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:
parent
b32b4b5bda
commit
d82738e17e
6 changed files with 161 additions and 9 deletions
|
|
@ -401,13 +401,16 @@ class _EventDetailDialogState extends State<_EventDetailDialog> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _explain() async {
|
||||
Future<void> _explain({bool forceFresh = false}) async {
|
||||
setState(() {
|
||||
_explaining = true;
|
||||
_explanation = null;
|
||||
});
|
||||
final prompt = _buildPrompt(event, _aiStatus?.privacyMode ?? 'off');
|
||||
final result = await HubService.instance.askAi(prompt);
|
||||
final result = await HubService.instance.askAi(
|
||||
prompt,
|
||||
forceFresh: forceFresh,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_explaining = false;
|
||||
|
|
@ -536,6 +539,9 @@ class _EventDetailDialogState extends State<_EventDetailDialog> {
|
|||
explaining: _explaining,
|
||||
result: _explanation,
|
||||
privacyMode: _aiStatus?.privacyMode ?? 'off',
|
||||
onRegenerate: _explaining
|
||||
? null
|
||||
: () => _explain(forceFresh: true),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
|
@ -626,11 +632,16 @@ class _ExplanationPanel extends StatelessWidget {
|
|||
final bool explaining;
|
||||
final AskAiResult? result;
|
||||
final String privacyMode;
|
||||
/// Triggered by the "Regenerate" affordance — passes
|
||||
/// `forceFresh: true` so the next askAi skips the cache and
|
||||
/// hits the live provider. Null while [explaining] is true.
|
||||
final VoidCallback? onRegenerate;
|
||||
|
||||
const _ExplanationPanel({
|
||||
required this.explaining,
|
||||
required this.result,
|
||||
required this.privacyMode,
|
||||
required this.onRegenerate,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -671,15 +682,42 @@ class _ExplanationPanel extends StatelessWidget {
|
|||
? FaiPillTone.warning
|
||||
: FaiPillTone.neutral,
|
||||
),
|
||||
if (result != null && result!.isSuccess && result!.cached) ...[
|
||||
const SizedBox(width: FaiSpace.xs),
|
||||
Tooltip(
|
||||
message:
|
||||
'Served from the local cache. Originally generated '
|
||||
'${result!.cachedAt.isEmpty ? "at unknown time" : "on ${result!.cachedAt}"}'
|
||||
'${result!.cacheHits > 1 ? " · ${result!.cacheHits} hits" : ""}.',
|
||||
child: const FaiPill(
|
||||
label: 'cached',
|
||||
tone: FaiPillTone.success,
|
||||
icon: Icons.bolt,
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
if (result != null && result!.isSuccess && result!.latencyMs > 0)
|
||||
Text(
|
||||
'${result!.latencyMs} ms',
|
||||
result!.cached
|
||||
? 'orig ${result!.latencyMs} ms'
|
||||
: '${result!.latencyMs} ms',
|
||||
style: FaiTheme.mono(
|
||||
size: 10,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (result != null && result!.isSuccess && onRegenerate != null) ...[
|
||||
const SizedBox(width: FaiSpace.sm),
|
||||
Tooltip(
|
||||
message: 'Regenerate — skip cache, ask the model again',
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.refresh, size: 14),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: onRegenerate,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: FaiSpace.sm),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue