UX: Use new DStatTiles reusable component from core (#1025)

For the Spam and Usage tabs in admin
This commit is contained in:
Martin Brennan 2024-12-16 16:48:46 +10:00 committed by GitHub
parent 94b85ece80
commit 222e2cf4f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 65 additions and 122 deletions

View File

@ -6,6 +6,7 @@ import { action } from "@ember/object";
import { LinkTo } from "@ember/routing"; import { LinkTo } from "@ember/routing";
import { service } from "@ember/service"; import { service } from "@ember/service";
import DButton from "discourse/components/d-button"; import DButton from "discourse/components/d-button";
import DStatTiles from "discourse/components/d-stat-tiles";
import DToggleSwitch from "discourse/components/d-toggle-switch"; import DToggleSwitch from "discourse/components/d-toggle-switch";
import DTooltip from "discourse/components/d-tooltip"; import DTooltip from "discourse/components/d-tooltip";
import withEventValue from "discourse/helpers/with-event-value"; import withEventValue from "discourse/helpers/with-event-value";
@ -121,7 +122,7 @@ export default class AiSpam extends Component {
get metrics() { get metrics() {
const detected = { const detected = {
label: "discourse_ai.spam.spam_detected", label: i18n("discourse_ai.spam.spam_detected"),
value: this.stats.spam_detected, value: this.stats.spam_detected,
}; };
if (this.args.model.flagging_username) { if (this.args.model.flagging_username) {
@ -131,17 +132,19 @@ export default class AiSpam extends Component {
} }
return [ return [
{ {
label: "discourse_ai.spam.scanned_count", label: i18n("discourse_ai.spam.scanned_count"),
value: this.stats.scanned_count, value: this.stats.scanned_count,
}, },
detected, detected,
{ {
label: "discourse_ai.spam.false_positives", label: i18n("discourse_ai.spam.false_positives"),
value: this.stats.false_positives, value: this.stats.false_positives,
tooltip: i18n("discourse_ai.spam.stat_tooltips.incorrectly_flagged"),
}, },
{ {
label: "discourse_ai.spam.false_negatives", label: i18n("discourse_ai.spam.false_negatives"),
value: this.stats.false_negatives, value: this.stats.false_negatives,
tooltip: i18n("discourse_ai.spam.stat_tooltips.missed_spam"),
}, },
]; ];
} }
@ -220,22 +223,16 @@ export default class AiSpam extends Component {
class="ai-spam__stats" class="ai-spam__stats"
> >
<:content> <:content>
<div class="ai-spam__metrics"> <DStatTiles as |tiles|>
{{#each this.metrics as |metric|}} {{#each this.metrics as |metric|}}
<div class="ai-spam__metrics-item"> <tiles.Tile
<span class="ai-spam__metrics-label">{{i18n @label={{metric.label}}
metric.label @url={{metric.href}}
}}</span> @value={{metric.value}}
{{#if metric.href}} @tooltip={{metric.tooltip}}
<a href={{metric.href}} class="ai-spam__metrics-value"> />
{{metric.value}}
</a>
{{else}}
<span class="ai-spam__metrics-value">{{metric.value}}</span>
{{/if}}
</div>
{{/each}} {{/each}}
</div> </DStatTiles>
</:content> </:content>
</AdminConfigAreaCard> </AdminConfigAreaCard>
</div> </div>

View File

@ -7,6 +7,7 @@ import { service } from "@ember/service";
import { eq, gt, lt } from "truth-helpers"; import { eq, gt, lt } from "truth-helpers";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner"; import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DButton from "discourse/components/d-button"; import DButton from "discourse/components/d-button";
import DStatTiles from "discourse/components/d-stat-tiles";
import DateTimeInputRange from "discourse/components/date-time-input-range"; import DateTimeInputRange from "discourse/components/date-time-input-range";
import avatar from "discourse/helpers/avatar"; import avatar from "discourse/helpers/avatar";
import concatClass from "discourse/helpers/concat-class"; import concatClass from "discourse/helpers/concat-class";
@ -124,6 +125,36 @@ export default class AiUsage extends Component {
return normalized; return normalized;
} }
get metrics() {
return [
{
label: i18n("discourse_ai.usage.total_requests"),
value: this.data.summary.total_requests,
tooltip: i18n("discourse_ai.usage.stat_tooltips.total_requests"),
},
{
label: i18n("discourse_ai.usage.total_tokens"),
value: this.data.summary.total_tokens,
tooltip: i18n("discourse_ai.usage.stat_tooltips.total_tokens"),
},
{
label: i18n("discourse_ai.usage.request_tokens"),
value: this.data.summary.total_request_tokens,
tooltip: i18n("discourse_ai.usage.stat_tooltips.request_tokens"),
},
{
label: i18n("discourse_ai.usage.response_tokens"),
value: this.data.summary.total_response_tokens,
tooltip: i18n("discourse_ai.usage.stat_tooltips.response_tokens"),
},
{
label: i18n("discourse_ai.usage.cached_tokens"),
value: this.data.summary.total_cached_tokens,
tooltip: i18n("discourse_ai.usage.stat_tooltips.cached_tokens"),
},
];
}
get chartConfig() { get chartConfig() {
if (!this.data?.data) { if (!this.data?.data) {
return; return;
@ -344,53 +375,16 @@ export default class AiUsage extends Component {
class="ai-usage__summary" class="ai-usage__summary"
> >
<:content> <:content>
<div class="ai-usage__summary-stats"> <DStatTiles as |tiles|>
<div class="ai-usage__summary-stat"> {{#each this.metrics as |metric|}}
<span class="label">{{i18n <tiles.Tile
"discourse_ai.usage.total_requests" @label={{metric.label}}
}}</span> @href={{metric.href}}
<span @value={{metric.value}}
class="value" @tooltip={{metric.tooltip}}
title={{this.data.summary.total_requests}} />
>{{number this.data.summary.total_requests}}</span> {{/each}}
</div> </DStatTiles>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.total_tokens"
}}</span>
<span
class="value"
title={{this.data.summary.total_tokens}}
>{{number this.data.summary.total_tokens}}</span>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.request_tokens"
}}</span>
<span
class="value"
title={{this.data.summary.total_request_tokens}}
>{{number this.data.summary.total_request_tokens}}</span>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.response_tokens"
}}</span>
<span
class="value"
title={{this.data.summary.total_response_tokens}}
>{{number this.data.summary.total_response_tokens}}</span>
</div>
<div class="ai-usage__summary-stat">
<span class="label">{{i18n
"discourse_ai.usage.cached_tokens"
}}</span>
<span
class="value"
title={{this.data.summary.total_cached_tokens}}
>{{number this.data.summary.total_cached_tokens}}</span>
</div>
</div>
</:content> </:content>
</AdminConfigAreaCard> </AdminConfigAreaCard>

View File

@ -45,37 +45,6 @@
&__stats { &__stats {
margin-top: 2em; margin-top: 2em;
} }
&__stats-title {
margin-bottom: 1em;
}
&__metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1em;
margin-bottom: 2em;
}
&__metrics-item {
display: flex;
flex-direction: column;
padding: 1em;
background: var(--primary-very-low);
border-radius: 0.25em;
}
&__metrics-label {
color: var(--primary-medium);
font-size: 0.875em;
margin-bottom: 0.5em;
}
&__metrics-value {
color: var(--primary);
font-size: 1.5em;
font-weight: bold;
}
} }
.spam-test-modal { .spam-test-modal {

View File

@ -68,32 +68,6 @@
font-size: 1.2em; font-size: 1.2em;
} }
&__summary-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1em;
}
&__summary-stat {
display: flex;
flex-direction: column;
padding: 1em;
background: var(--primary-very-low);
border-radius: 0.25em;
.label {
color: var(--primary-medium);
font-size: 0.875em;
margin-bottom: 0.5em;
}
.value {
color: var(--primary);
font-size: 1.5em;
font-weight: bold;
}
}
&__charts { &__charts {
margin-top: 2em; margin-top: 2em;
} }

View File

@ -157,6 +157,9 @@ en:
run: "Run test" run: "Run test"
spam: "Spam" spam: "Spam"
not_spam: "Not spam" not_spam: "Not spam"
stat_tooltips:
incorrectly_flagged: "Items that the AI bot flagged as spam where moderators disagreed"
missed_spam: "Items flagged by the community as spam that were not detected by the AI bot, which moderators agreed with"
usage: usage:
short_title: "Usage" short_title: "Usage"
@ -182,6 +185,12 @@ en:
no_models: "No model usage data found" no_models: "No model usage data found"
no_features: "No feature usage data found" no_features: "No feature usage data found"
subheader_description: "Tokens are the basic units that LLMs use to understand and generate text, usage data may affect costs." subheader_description: "Tokens are the basic units that LLMs use to understand and generate text, usage data may affect costs."
stat_tooltips:
total_requests: "All requests made to LLMs through Discourse"
total_tokens: "All the tokens used when prompting an LLM"
request_tokens: "Tokens used when the LLM tries to understand what you are saying"
response_tokens: "Tokens used when the LLM responds to your prompt"
cached_tokens: "Previously processed request tokens that the LLM reuses to optimize performance and cost"
periods: periods:
last_day: "Last 24 hours" last_day: "Last 24 hours"
last_week: "Last week" last_week: "Last week"