fix(aiops): 保存配置后分析页状态即时同步;分析页布局重排
CI / Backend (go vet + test) (push) Waiting to run
CI / Frontend (lint + build + test) (push) Waiting to run

- enabled 状态在 saveConfig 成功后立即 setEnabled(此前只更新 config,
  导致'AI 分析未启用'横幅要等下次请求才消失)
- 布局:工具栏改 4 列网格(数据源/时间/应用/问题),执行按钮与提示移到
  分隔线下右对齐;未启用横幅加 ✦ 图标 + 「去配置」直达按钮;空态/加载态
  加图标居中;消耗行改统计卡风格(数值 tabular 高亮)
This commit is contained in:
opsx committed 2026-11-01 00:26:22 +08:00
1 parent 5d898e3592
commit 2efbed882a
3 files changed
+39 -27

No files matched your search

+1
View File
@@ -20,6 +20,7 @@ export default {
}, },
analyze: { analyze: {
goConfig: 'Open config',
appScope: 'App scope', appScope: 'App scope',
allApps: 'All apps', allApps: 'All apps',
appRequired: 'Required (over 1h)', appRequired: 'Required (over 1h)',
+1
View File
@@ -20,6 +20,7 @@ export default {
}, },
analyze: { analyze: {
goConfig: '去配置',
appScope: '应用范围', appScope: '应用范围',
allApps: '全部应用', allApps: '全部应用',
appRequired: '必选(超过 1 小时)', appRequired: '必选(超过 1 小时)',
+37 -27
View File
@@ -190,6 +190,7 @@ export default function AiopsPage() {
.filter(Boolean), .filter(Boolean),
}) })
setConfig(res.config) setConfig(res.config)
setEnabled(Boolean(res.config?.enabled))
setApiKeyInput('') setApiKeyInput('')
toast.success('配置已保存') toast.success('配置已保存')
} catch (err) { } catch (err) {
@@ -245,13 +246,16 @@ export default function AiopsPage() {
{/* ─────────── 分析 ─────────── */} {/* ─────────── 分析 ─────────── */}
<TabsContent value="analyze" className="space-y-4"> <TabsContent value="analyze" className="space-y-4">
{!enabled && ( {!enabled && (
<div className="rounded-md border border-warning/40 bg-warning/10 px-3 py-2 text-sm"> <div className="flex flex-wrap items-center gap-3 rounded-lg border border-warning/40 bg-warning/10 px-4 py-3 text-sm">
{t('analyze.notEnabled')} <Sparkles className="size-4 shrink-0 text-warning" />
<span className="flex-1">{t('analyze.notEnabled')}</span>
<Button size="sm" variant="outline" onClick={() => setTab('config')}>{t('analyze.goConfig')}</Button>
</div> </div>
)} )}
<Card> <Card>
<CardContent className="flex flex-wrap items-end gap-3 pt-4"> <CardContent className="pt-4">
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div className="min-w-40 space-y-1"> <div className="min-w-40 space-y-1">
<Label className="text-xs">{t('analyze.source')}</Label> <Label className="text-xs">{t('analyze.source')}</Label>
<select <select
@@ -299,34 +303,41 @@ export default function AiopsPage() {
</select> </select>
{needApp && <p className="text-[11px] text-warning">{t('analyze.appScopeHint')}</p>} {needApp && <p className="text-[11px] text-warning">{t('analyze.appScopeHint')}</p>}
</div> </div>
<div className="min-w-64 flex-1 space-y-1"> <div className="space-y-1 xl:col-span-2">
<Label className="text-xs">{t('analyze.question')}</Label> <Label className="text-xs">{t('analyze.question')}</Label>
<Input <Input
value={question} value={question}
placeholder={t('analyze.questionPlaceholder')} placeholder={t('analyze.questionPlaceholder')}
onChange={e => setQuestion(e.target.value)} onChange={e => setQuestion(e.target.value)}
className="h-9" className="h-9"
/> />
</div>
</div>
<div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t pt-3">
<p className="text-xs text-muted-foreground">{t('analyze.runHint')}</p>
<Button onClick={runAnalysis} disabled={running || !enabled || (needApp && !app)} className="h-9">
{running ? <Loader2 className="mr-1.5 h-4 w-4 animate-spin" /> : <Play className="mr-1.5 h-4 w-4" />}
{running ? t('analyze.running') : t('analyze.run')}
</Button>
</div> </div>
<Button onClick={runAnalysis} disabled={running || !enabled || (needApp && !app)} className="h-9">
{running ? <Loader2 className="mr-1.5 h-4 w-4 animate-spin" /> : <Play className="mr-1.5 h-4 w-4" />}
{running ? t('analyze.running') : t('analyze.run')}
</Button>
</CardContent> </CardContent>
<div className="px-4 pb-3 text-xs text-muted-foreground">{t('analyze.runHint')}</div>
</Card> </Card>
{!outcome && !running && ( {!outcome && !running && (
<Card> <Card>
<CardContent className="py-10 text-center text-sm text-muted-foreground">{t('analyze.empty')}</CardContent> <CardContent className="flex flex-col items-center justify-center gap-2 py-14 text-center">
<Sparkles className="size-8 text-muted-foreground/40" />
<p className="text-sm text-muted-foreground">{t('analyze.empty')}</p>
</CardContent>
</Card> </Card>
)} )}
{running && ( {running && (
<Card> <Card>
<CardContent className="flex items-center justify-center gap-2 py-10 text-sm text-muted-foreground"> <CardContent className="flex flex-col items-center justify-center gap-2 py-14 text-center">
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="size-7 animate-spin text-primary" />
{t('analyze.running')} <p className="text-sm font-medium">{t('analyze.running')}</p>
<p className="text-xs text-muted-foreground">{t('analyze.runHint')}</p>
</CardContent> </CardContent>
</Card> </Card>
)} )}
@@ -334,13 +345,12 @@ export default function AiopsPage() {
{outcome && ( {outcome && (
<> <>
{/* 本次消耗:让成本和延迟是可见的 */} {/* 本次消耗:让成本和延迟是可见的 */}
<div className="flex flex-wrap items-center gap-4 rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground"> <div className="flex flex-wrap items-center gap-x-5 gap-y-1 rounded-lg border bg-muted/30 px-4 py-2.5 text-xs">
<span> <span className="font-medium text-foreground">{t('analyze.lastRun')}</span>
{t('analyze.lastRun')}:{t('analyze.promptTokens')} {fmtNum(outcome.run.promptTokens)} ·{' '} <span className="text-muted-foreground">{t('analyze.promptTokens')} <span className="tabular-nums text-foreground">{fmtNum(outcome.run.promptTokens)}</span></span>
{t('analyze.completionTokens')} {fmtNum(outcome.run.completionTokens)} · {t('analyze.duration')}{' '} <span className="text-muted-foreground">{t('analyze.completionTokens')} <span className="tabular-nums text-foreground">{fmtNum(outcome.run.completionTokens)}</span></span>
{(outcome.run.durationMs / 1000).toFixed(1)}s <span className="text-muted-foreground">{t('analyze.duration')} <span className="tabular-nums text-foreground">{(outcome.run.durationMs / 1000).toFixed(1)}s</span></span>
</span> <Button variant="link" size="sm" className="ml-auto h-auto p-0 text-xs" onClick={() => setPromptOpen(true)}>
<Button variant="link" size="sm" className="h-auto p-0 text-xs" onClick={() => setPromptOpen(true)}>
{t('analyze.viewPrompt')} {t('analyze.viewPrompt')}
</Button> </Button>
</div> </div>