#frozen_string_literal: true module DiscourseAi::AiBot::Commands MAX_CONTEXT_TOKENS = 2000 class SettingContextCommand < Command def self.rg_installed? if defined?(@rg_installed) @rg_installed else @rg_installed = begin Discourse::Utils.execute_command("which", "rg") true rescue Discourse::Utils::CommandError false end end end class << self def name "setting_context" end def desc "Will provide you with full context regarding a particular site setting in Discourse" end def parameters [ Parameter.new( name: "setting_name", description: "The name of the site setting we need context for", type: "string", required: true, ), ] end end def result_name "context" end def description_args { setting_name: @setting_name } end CODE_FILE_EXTENSIONS = "rb,js,gjs,hbs" def process(setting_name:) if !self.class.rg_installed? return( { setting_name: setting_name, context: "This command requires the rg command line tool to be installed on the server", } ) end @setting_name = setting_name if !SiteSetting.has_setting?(setting_name) { setting_name: setting_name, context: "This setting does not exist" } else description = SiteSetting.description(setting_name) result = +"# #{setting_name}\n#{description}\n\n" setting_info = find_setting_info(setting_name, [Rails.root.join("config", "site_settings.yml").to_s]) if !setting_info setting_info = find_setting_info(setting_name, Dir[Rails.root.join("plugins/**/settings.yml")]) end result << setting_info result << "\n\n" %w[lib app plugins].each do |dir| path = Rails.root.join(dir).to_s result << Discourse::Utils.execute_command( "rg", setting_name, path, "-g", "!**/spec/**", "-g", "!**/dist/**", "-g", "*.{#{CODE_FILE_EXTENSIONS}}", "-C", "10", "--color", "never", "--heading", "--no-ignore", chdir: path, success_status_codes: [0, 1], ) end result.gsub!(/^#{Regexp.escape(Rails.root.to_s)}/, "") result = tokenizer.truncate(result, MAX_CONTEXT_TOKENS) { setting_name: setting_name, context: result } end end def find_setting_info(name, paths) path, result = nil paths.each do |search_path| result = Discourse::Utils.execute_command( "rg", name, search_path, "-g", "*.{#{CODE_FILE_EXTENSIONS}}", "-A", "10", "--color", "never", "--heading", success_status_codes: [0, 1], ) if !result.blank? path = search_path break end end if result.blank? nil else rows = result.split("\n") leading_spaces = rows[0].match(/^\s*/)[0].length filtered = [] rows.each do |row| if !filtered.blank? break if row.match(/^\s*/)[0].length <= leading_spaces end filtered << row end filtered.unshift("#{path}") filtered.join("\n") end end end end