0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 01:10:43 +08:00
discourse/plugins/discourse-ai/config/routes.rb
Natalie Tay 3507a634a5
FEATURE: Show progress of other translation targets in the AI translation admin page (#41983)
Related:
https://meta.discourse.org/t/ai-translation-progress-graph/405915/3

A more thorough translation progress screen.

Progress now covers posts, topics, categories and tags. The overview
cards show the total content in scope, how many are fully translated
across all supported locales, and how many still need language
detection.

Eligibility follows the existing translation settings, including max
age, category scope, bot content and PM settings. Tags don't have
eligibility settings, so they use the total instead.

Opening a card loads its per-locale breakdown separately, so the initial
page load doesn't also run every detail query. For each locale, content
already in that language is excluded from eligible. Pending includes
eligible content that still needs language detection.

The posts query is quite heavy, so the overview is cached together for
2h and each detail target has its own 2h cache. Relevant translation
settings are part of the cache key, and a distributed mutex prevents
duplicate detail queries when two requests miss the same cache.

**Perf note**: Calculating translation progress can be expensive on
large sites, particularly for posts. The production dataset used for
testing contained approximately 2 million posts, 2.5 million post
localizations, and 730,000 topic localizations. Post eligibility must
also inspect `raw` for the empty and maximum-length rules, so the
candidate scope cannot be answered entirely from the existing indexes.
To limit the impact we do have cache, and details are fetched only when
a card is opened, and the queries aggregate localization coverage in
bulk instead of repeatedly probing the localization indexes for every
eligible record and target locale.

Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
2026-07-24 12:24:51 +08:00

196 lines
8.1 KiB
Ruby
Vendored

# frozen_string_literal: true
DiscourseAi::Engine.routes.draw do
scope path: "/credits", defaults: { format: :json } do
get "status" => "ai_credits#status"
end
scope path: "/post-image-captions", defaults: { format: :json } do
get ":post_id" => "post_image_captions#index"
put ":post_id/:base62_sha1" => "post_image_captions#update"
end
scope module: :ai_helper, path: "/ai-helper", defaults: { format: :json } do
post "suggest" => "assistant#suggest"
post "suggest_title" => "assistant#suggest_title"
post "suggest_category" => "assistant#suggest_category"
post "suggest_tags" => "assistant#suggest_tags"
post "stream_suggestion" => "assistant#stream_suggestion"
end
scope module: :embeddings, path: "/embeddings", defaults: { format: :json } do
get "semantic-search" => "embeddings#search"
get "quick-search" => "embeddings#quick_search"
end
scope module: :discord, path: "/discord", defaults: { format: :json } do
post "interactions" => "bot#interactions"
end
scope module: :ai_bot, path: "/ai-bot", defaults: { format: :json } do
get "bot-username" => "bot#show_bot_username"
get "post/:post_id/show-debug-info" => "bot#show_debug_info"
get "show-debug-info/:id" => "bot#show_debug_info_by_id"
post "post/:post_id/stop-streaming" => "bot#stop_streaming_response"
post "post/:post_id/retry" => "bot#retry_response"
end
scope module: :discover, path: "/discoveries", defaults: { format: :json } do
post "reply" => "discoveries#reply"
post "continue-convo" => "discoveries#continue_convo"
end
scope module: :ai_bot, path: "/ai-bot/shared-ai-conversations" do
post "/" => "shared_ai_conversations#create"
delete "/:share_key" => "shared_ai_conversations#destroy"
get "/:share_key" => "shared_ai_conversations#show"
get "/asset/:version/:name" => "shared_ai_conversations#asset"
get "/preview/:topic_id" => "shared_ai_conversations#preview"
end
scope module: :ai_bot, path: "/ai-bot/conversations" do
get "/" => "conversations#index"
post "/" => "conversations#create"
put "/:topic_id/starred" => "conversations#update_starred"
end
scope module: :ai_bot, path: "/ai-bot/artifacts" do
get "/:id" => "artifacts#show"
get "/:id/:version" => "artifacts#show"
end
scope module: :ai_bot, path: "/ai-bot/artifact-key-values/:artifact_id" do
get "/" => "artifact_key_values#index"
post "/" => "artifact_key_values#set"
delete "/:key" => "artifact_key_values#destroy"
delete "/" => "artifact_key_values#destroy"
end
scope module: :summarization, path: "/summarization", defaults: { format: :json } do
get "/t/:topic_id" => "summary#show", :constraints => { topic_id: /\d+/ }
post "/t/:topic_id" => "summary#create", :constraints => { topic_id: /\d+/ }
put "/regen_gist" => "summary#regen_gist"
put "/regen_summary" => "summary#regen_summary"
post "/channels/:channel_id" => "chat_summary#show"
end
scope module: :sentiment, path: "/sentiment", defaults: { format: :json } do
get "/posts" => "sentiment#posts", :constraints => StaffConstraint.new
end
scope module: :translation, path: "/translate", defaults: { format: :json } do
post "/posts/:post_id" => "translation#translate"
post "/topics/:topic_id" => "translation#schedule_topic"
end
scope path: "/mcp/oauth", defaults: { format: :json } do
get "client-metadata" => "mcp_oauth#client_metadata"
end
end
Discourse::Application.routes.draw do
mount DiscourseAi::Engine, at: "discourse-ai"
get "admin/dashboard/sentiment" => "discourse_ai/admin/dashboard#sentiment",
:constraints => StaffConstraint.new
scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do
get "/admin-dashboard-highlights" => "discourse_ai/admin/admin_dashboard_highlights#show",
:format => :json
get "/ai-personas", to: redirect("/admin/plugins/discourse-ai/ai-agents")
get "/ai-personas/new", to: redirect("/admin/plugins/discourse-ai/ai-agents/new")
get "/ai-personas/:id/edit", to: redirect("/admin/plugins/discourse-ai/ai-agents/%{id}/edit")
resources :ai_artifacts,
only: %i[index show create update destroy],
path: "ai-artifacts",
controller: "discourse_ai/admin/ai_artifacts",
defaults: {
format: :json,
}
resources :ai_agents,
only: %i[index new create edit update destroy],
path: "ai-agents",
controller: "discourse_ai/admin/ai_agents"
post "/ai-agents/stream-reply" => "discourse_ai/admin/ai_agents#stream_reply"
post "/ai-agents/:id/create-user", to: "discourse_ai/admin/ai_agents#create_user"
get "/ai-agents/:id/export", to: "discourse_ai/admin/ai_agents#export", format: :json
post "/ai-agents/import", to: "discourse_ai/admin/ai_agents#import"
put "/ai-agents/:id/files/remove", to: "discourse_ai/admin/ai_agents#remove_file"
get "/ai-agents/:id/files/status", to: "discourse_ai/admin/ai_agents#indexing_status_check"
resources(
:ai_tools,
only: %i[index new create edit update destroy],
path: "ai-tools",
controller: "discourse_ai/admin/ai_tools",
)
post "/ai-tools/:id/test", to: "discourse_ai/admin/ai_tools#test"
get "/ai-tools/:id/export", to: "discourse_ai/admin/ai_tools#export", format: :json
post "/ai-tools/import", to: "discourse_ai/admin/ai_tools#import"
get "/ai-tools/mcp-servers/new", to: "discourse_ai/admin/ai_mcp_servers#new"
get "/ai-tools/mcp-servers/:id/edit", to: "discourse_ai/admin/ai_mcp_servers#edit"
resources :ai_mcp_servers,
only: %i[index new create edit update destroy],
path: "ai-mcp-servers",
controller: "discourse_ai/admin/ai_mcp_servers"
post "/ai-mcp-servers/test", to: "discourse_ai/admin/ai_mcp_servers#test"
post "/ai-mcp-servers/:id/test", to: "discourse_ai/admin/ai_mcp_servers#test"
get "/ai-mcp-servers/:id/oauth/start", to: "discourse_ai/admin/ai_mcp_servers#oauth_start"
get "/ai-mcp-servers/oauth/callback", to: "discourse_ai/admin/ai_mcp_servers#oauth_callback"
delete "/ai-mcp-servers/:id/oauth/disconnect",
to: "discourse_ai/admin/ai_mcp_servers#oauth_disconnect"
post "/rag-document-fragments/files/upload",
to: "discourse_ai/admin/rag_document_fragments#upload_file"
get "/rag-document-fragments/files/status",
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"
get "/ai-spam", to: "discourse_ai/admin/ai_spam#show"
put "/ai-spam", to: "discourse_ai/admin/ai_spam#update"
post "/ai-spam/test", to: "discourse_ai/admin/ai_spam#test"
post "/ai-spam/fix-errors", to: "discourse_ai/admin/ai_spam#fix_errors"
get "/ai-translations", to: "discourse_ai/admin/ai_translations#show"
get "/ai-translations/progress", to: "discourse_ai/admin/ai_translations#progress"
get "/ai-translations/progress/:target_type",
to: "discourse_ai/admin/ai_translations#progress_detail"
post "/ai-theme-translations", to: "discourse_ai/admin/ai_theme_translations#create"
resources :ai_llms,
only: %i[index new create edit update destroy],
path: "ai-llms",
controller: "discourse_ai/admin/ai_llms" do
collection { post :test }
end
resources :ai_llm_quotas,
controller: "discourse_ai/admin/ai_llm_quotas",
path: "quotas",
only: %i[index create update destroy]
resources :ai_embeddings,
only: %i[index new create edit update destroy],
path: "ai-embeddings",
controller: "discourse_ai/admin/ai_embeddings" do
collection { post :test }
end
resources :ai_features,
only: %i[index edit],
path: "ai-features",
controller: "discourse_ai/admin/ai_features"
resources :ai_secrets,
only: %i[index show new create edit update destroy],
path: "ai-secrets",
controller: "discourse_ai/admin/ai_secrets"
end
end