UX: Improve rough edges of AI usage page (#1014)

* UX: Improve rough edges of AI usage page

* Ensure all text uses I18n
* Change from <button> usage to <DButton>
* Use <AdminConfigAreaCard> in place of custom card styles
* Format numbers nicely using our number format helper,
  show full values on hover using title attr
* Ensure 0 is always shown for counters, instead of being blank

* FEATURE: Load usage data after page load

Use ConditionalLoadingSpinner to hide load of usage
data, this prevents us hanging on page load with a white
screen.

* UX: Split users table, and add empty placeholders and page subheader

* DEV: Test fix
This commit is contained in:
Martin Brennan
2024-12-12 08:55:24 +11:00
committed by GitHub
parent a4440c507b
commit ae80494448
7 changed files with 395 additions and 249 deletions
@@ -6,6 +6,9 @@ module DiscourseAi
requires_plugin "discourse-ai"
def show
end
def report
render json: AiUsageSerializer.new(create_report, root: false)
end
File diff suppressed because it is too large Load Diff
@@ -60,9 +60,6 @@
&__summary {
margin: 2em 0;
padding: 1.5em;
background: var(--primary-very-low);
border-radius: 0.5em;
}
&__summary-title {
@@ -81,7 +78,7 @@
display: flex;
flex-direction: column;
padding: 1em;
background: var(--secondary);
background: var(--primary-very-low);
border-radius: 0.25em;
.label {
@@ -120,22 +117,38 @@
margin-top: 2em;
@media (max-width: 768px) {
grid-template-columns: 1fr;
grid-template-columns: none;
display: flex;
flex-direction: column;
}
}
&__features,
&__users,
&__models {
background: var(--primary-very-low);
padding: 1em;
border-radius: 0.5em;
}
&__users {
grid-column: span 2;
&__features-title,
&__users-title,
&__models-title {
margin-bottom: 1em;
.admin-config-area-card__content {
display: flex;
.ai-usage__users-table {
&:first-child {
margin-right: 2em;
&.-double-width {
margin-right: 0;
}
}
&.-double-width {
.ai-usage__users-username {
width: auto;
}
}
.ai-usage__users-username {
width: 50px;
}
}
}
}
&__features-table,
+11
View File
@@ -146,7 +146,18 @@ en:
total_requests: "Total requests"
request_tokens: "Request tokens"
response_tokens: "Response tokens"
net_request_tokens: "Net request tokens"
cached_tokens: "Cached tokens"
cached_request_tokens: "Cached request tokens"
no_users: "No user usage data found"
no_models: "No model 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."
periods:
last_day: "Last 24 hours"
last_week: "Last week"
last_month: "Last month"
custom: "Custom..."
ai_persona:
tool_strategies:
+1
View File
@@ -79,6 +79,7 @@ Discourse::Application.routes.draw do
to: "discourse_ai/admin/rag_document_fragments#indexing_status_check"
get "/ai-usage", to: "discourse_ai/admin/ai_usage#show"
get "/ai-usage-report", to: "discourse_ai/admin/ai_usage#report"
resources :ai_llms,
only: %i[index create show update destroy],
+20 -20
View File
@@ -14,33 +14,33 @@ module DiscourseAi
end
def total_tokens
stats.total_tokens
stats.total_tokens || 0
end
def total_cached_tokens
stats.total_cached_tokens
stats.total_cached_tokens || 0
end
def total_request_tokens
stats.total_request_tokens
stats.total_request_tokens || 0
end
def total_response_tokens
stats.total_response_tokens
stats.total_response_tokens || 0
end
def total_requests
stats.total_requests
stats.total_requests || 0
end
def stats
@stats ||=
base_query.select(
"COUNT(*) as total_requests",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)[
0
]
@@ -66,10 +66,10 @@ module DiscourseAi
.order("DATE_TRUNC('#{period}', created_at)")
.select(
"DATE_TRUNC('#{period}', created_at) as period",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)
end
@@ -83,10 +83,10 @@ module DiscourseAi
"users.username",
"users.uploaded_avatar_id",
"COUNT(*) as usage_count",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)
end
@@ -97,10 +97,10 @@ module DiscourseAi
.select(
"case when coalesce(feature_name, '') = '' then '#{UNKNOWN_FEATURE}' else feature_name end as feature_name",
"COUNT(*) as usage_count",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)
end
@@ -111,10 +111,10 @@ module DiscourseAi
.select(
"language_model as llm",
"COUNT(*) as usage_count",
"SUM(request_tokens + response_tokens) as total_tokens",
"SUM(COALESCE(request_tokens + response_tokens, 0)) as total_tokens",
"SUM(COALESCE(cached_tokens,0)) as total_cached_tokens",
"SUM(request_tokens) as total_request_tokens",
"SUM(response_tokens) as total_response_tokens",
"SUM(COALESCE(request_tokens,0)) as total_request_tokens",
"SUM(COALESCE(response_tokens,0)) as total_response_tokens",
)
end
+18 -10
View File
@@ -5,7 +5,7 @@ require "rails_helper"
RSpec.describe DiscourseAi::Admin::AiUsageController do
fab!(:admin)
fab!(:user)
let(:usage_path) { "/admin/plugins/discourse-ai/ai-usage.json" }
let(:usage_report_path) { "/admin/plugins/discourse-ai/ai-usage-report.json" }
before { SiteSetting.discourse_ai_enabled = true }
@@ -36,7 +36,7 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "returns correct data structure" do
get usage_path
get usage_report_path
expect(response.status).to eq(200)
@@ -48,14 +48,18 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "respects date filters" do
get usage_path, params: { start_date: 3.days.ago.to_date, end_date: 1.day.ago.to_date }
get usage_report_path,
params: {
start_date: 3.days.ago.to_date,
end_date: 1.day.ago.to_date,
}
json = response.parsed_body
expect(json["summary"]["total_tokens"]).to eq(450) # sum of all tokens
end
it "filters by feature" do
get usage_path, params: { feature: "summarize" }
get usage_report_path, params: { feature: "summarize" }
json = response.parsed_body
@@ -66,7 +70,7 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "filters by model" do
get usage_path, params: { model: "gpt-3.5" }
get usage_report_path, params: { model: "gpt-3.5" }
json = response.parsed_body
models = json["models"]
@@ -76,10 +80,10 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "handles different period groupings" do
get usage_path, params: { period: "hour" }
get usage_report_path, params: { period: "hour" }
expect(response.status).to eq(200)
get usage_path, params: { period: "month" }
get usage_report_path, params: { period: "month" }
expect(response.status).to eq(200)
end
end
@@ -102,7 +106,11 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "returns hourly data when period is day" do
get usage_path, params: { start_date: 1.day.ago.to_date, end_date: Time.current.to_date }
get usage_report_path,
params: {
start_date: 1.day.ago.to_date,
end_date: Time.current.to_date,
}
expect(response.status).to eq(200)
json = response.parsed_body
@@ -121,7 +129,7 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
before { sign_in(user) }
it "blocks access" do
get usage_path
get usage_report_path
expect(response.status).to eq(404)
end
end
@@ -133,7 +141,7 @@ RSpec.describe DiscourseAi::Admin::AiUsageController do
end
it "returns error" do
get usage_path
get usage_report_path
expect(response.status).to eq(404)
end
end