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:
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user