class Huggingface
Constants
- ACCOUNT_KEY
- BASE_URL
- COMPUTE_HARDWARE_URL
- DEFAULT_LIMIT
- HARDWARE_API_URLS
- HARDWARE_LIST_KEYS
- HARDWARE_URL
- MODELS_URL
- PLUGIN_NAME
- STYLE_BANNER
- WHOAMI_URL
Public Class Methods
Source
# File plugins/huggingface.rb, line 158 def self.api_get(url, token: nil, fetch: nil) if fetch fetch.call(url, token: token) else live_get(url, token: token) end end
Source
# File plugins/huggingface.rb, line 397 def self.clear_account(network:, channel:, nick:) clear_account_record(network: network, channel: nil, nick: nick) clear_account_record(network: network, channel: channel, nick: nick) unless private_message?(channel) end
Source
# File plugins/huggingface.rb, line 402 def self.clear_account_record(network:, channel:, nick:) RabbotDb.set_plugin_value( plugin: PLUGIN_NAME, key: ACCOUNT_KEY, value: "", network: network, channel: channel, nick: nick ) end
Source
# File plugins/huggingface.rb, line 97 def self.command_pattern /hf(?:\s+(.+))?$/i end
Source
# File plugins/huggingface.rb, line 316 def self.device_display_name(entry) vendor = entry["vendor"] || entry["provider"] || entry["brand"] model = entry["modelName"] || entry["model"] || entry["product"] name = entry["name"] || entry["prettyName"] || entry["hardwareName"] || entry["label"] || entry["accelerator"] name = entry["sku"] if name.to_s.strip.empty? && entry["sku"].is_a?(String) if !vendor.to_s.strip.empty? && !model.to_s.strip.empty? combined = "#{vendor} #{model}".strip name = combined if name.to_s.strip.empty? || name == model elsif name.to_s.strip.empty? name = vendor || model end name.to_s.strip end
Source
# File plugins/huggingface.rb, line 464 def self.dispatch_command(args_text, network:, nick:, channel: nil, ai_allowed: false, fetch: nil) parsed = parse_command(args_text) if parsed[:action] == :error || parsed[:error] return { error: parsed[:error] || usage_message } end case parsed[:action] when :help { reply: format_help_reply } when :login if !private_message?(channel) return { error: login_channel_denied_message } end unless ai_allowed return { error: AiAccess.denial_message } end result = login_account( network: network, channel: channel, nick: nick, token: parsed[:token], fetch: fetch ) return { error: result[:error] } unless result[:success] { reply: format_login_reply(username: result[:username], hardware: result[:hardware]) } when :logout clear_account(network: network, channel: channel, nick: nick) { reply: format_logout_reply } when :whoami account = load_account(network: network, channel: channel, nick: nick) { reply: format_whoami_reply(account) } when :hardware account = load_account(network: network, channel: channel, nick: nick) if parsed[:mode] == :sync unless account return { error: "Not logged in — use /msg <bot> !hf login <token> first." } end result = sync_hardware(network: network, channel: channel, nick: nick, fetch: fetch) return { error: result[:error] } unless result[:success] { reply: format_hardware_reply(result[:hardware], synced: true) } else hardware = account&.dig("hardware") { reply: format_hardware_reply(hardware, synced: false) } end when :search unless ai_allowed return { error: AiAccess.denial_message } end account = load_account(network: network, channel: channel, nick: nick) { reply: run_search( query: parsed[:query], moderation: parsed[:moderation], account: account, fetch: fetch ) } when :recommend unless ai_allowed return { error: AiAccess.denial_message } end account = load_account(network: network, channel: channel, nick: nick) if account.nil? || account["token"].to_s.strip.empty? return { error: "Not logged in — use /msg <bot> !hf login <token> first." } end unless account_ready_for_recommend?(account) return { error: "No hardware profile — add GPUs at huggingface.co/settings/hardware then !hf hardware sync." } end { reply: run_recommend(account: account, fetch: fetch) } else { error: usage_message } end end
Source
# File plugins/huggingface.rb, line 228 def self.empty_hardware { primary_name: nil, primary_memory_gb: nil, device_count: 0, devices: [] } end
Source
# File plugins/huggingface.rb, line 241 def self.extract_device_list(payload) return payload if payload.is_a?(Array) return [] unless payload.is_a?(Hash) return [] if payload["error"] HARDWARE_LIST_KEYS.each do |key| list = payload[key] next if list.nil? return normalize_device_list(list) end [ %w[profile hardware], %w[profile devices], %w[data devices], %w[data hardware], %w[computeHardware devices], %w[user hardware] ].each do |path| node = path.reduce(payload) { |acc, key| acc.is_a?(Hash) ? acc[key] : nil } next if node.nil? return normalize_device_list(node) end [] end
Source
# File plugins/huggingface.rb, line 237 def self.extract_devices(payload) extract_device_list(payload).filter_map { |entry| normalize_device_entry(entry) } end
Source
# File plugins/huggingface.rb, line 375 def self.fetch_account(network:, channel:, nick:) RabbotDb.get_plugin_json( plugin: PLUGIN_NAME, key: ACCOUNT_KEY, network: network, channel: channel, nick: nick, default: nil ) end
Source
# File plugins/huggingface.rb, line 353 def self.fetch_compute_hardware(token, fetch: nil) best = empty_hardware HARDWARE_API_URLS.each do |url| body = api_get(url, token: token, fetch: fetch) hardware = parse_compute_hardware(parse_json(body)) return hardware if hardware_detected?(hardware) best = hardware if hardware[:device_count].to_i > best[:device_count].to_i rescue StandardError next end best end
Source
# File plugins/huggingface.rb, line 79 def fetch_recommendations(vram_gb:, token: nil, limit: DEFAULT_LIMIT, fetch: nil) fetcher = fetch || ->(url, token:) { api_get(url, token: token) } HuggingfaceModels.fetch_recommendations( vram_gb: vram_gb, token: token, limit: limit, fetch: fetcher ) end
Source
# File plugins/huggingface.rb, line 63 def format_help_reply HuggingfaceReport.format_help_reply(usage_message: usage_message) end
Source
# File plugins/huggingface.rb, line 59 def format_whoami_reply(account) HuggingfaceReport.format_whoami_reply(account, mask_token: method(:mask_token)) end
Source
# File plugins/huggingface.rb, line 166 def self.live_get(url, token:) uri = URI.parse(url) request = Net::HTTP::Get.new(uri) request["User-Agent"] = RabbotHttp::DEFAULT_USER_AGENT request["Authorization"] = "Bearer #{token}" unless token.to_s.strip.empty? response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http| http.request(request) end raise StandardError, "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess) response.body end
Source
# File plugins/huggingface.rb, line 367 def self.load_account(network:, channel:, nick:) account = fetch_account(network: network, channel: nil, nick: nick) return account if account return nil if private_message?(channel) fetch_account(network: network, channel: channel, nick: nick) end
Source
# File plugins/huggingface.rb, line 413 def self.login_account(network:, channel:, nick:, token:, fetch: nil) profile = verify_token(token, fetch: fetch) return profile unless profile[:success] hardware = fetch_compute_hardware(token, fetch: fetch) account = { "username" => profile[:username], "token" => token, "hardware" => stringify_hardware(hardware) } save_account(network: network, channel: channel, nick: nick, account: account) profile.merge(hardware: hardware) end
Source
# File plugins/huggingface.rb, line 103 def self.login_channel_denied_message "Login is private — use /msg <bot> !hf login <token> so your token is not shown in channel." end
Source
# File plugins/huggingface.rb, line 151 def self.mask_token(token) value = token.to_s.strip return "(hidden)" if value.length < 8 "#{value[0, 6]}…#{value[-3, 3]}" end
Source
# File plugins/huggingface.rb, line 286 def self.normalize_device_entry(entry) return nil unless entry.is_a?(Hash) source = entry.dup %w[spec hardware device].each do |nested| child = entry[nested] source = source.merge(child) if child.is_a?(Hash) end source["name"] = entry["sku"] if source["name"].to_s.strip.empty? && entry["sku"].is_a?(String) name = device_display_name(source) memory = parse_memory_gb( source["memoryGB"] || source["memoryGb"] || source["memory_gb"] || source["memoryInGB"] || source["memoryInGb"] || source["memorySize"] || source["vramGB"] || source["vramGb"] || source["vram"] || source["memory"] ) count = (source["count"] || source["unitCount"] || source["units"] || 1).to_i count = 1 unless count.positive? primary = truthy?(source["primary"]) || truthy?(source["isPrimary"]) || truthy?(source["is_primary"]) return nil if name.empty? && memory.nil? { name: name, memory_gb: memory, primary: primary, type: (source["type"] || source["kind"] || source["hardwareType"]).to_s, count: count } end
Source
# File plugins/huggingface.rb, line 270 def self.normalize_device_list(list) case list when Array list when Hash %w[devices hardware items setups entries userDevices].each do |key| child = list[key] return Array(child) if child.is_a?(Array) end list.values.grep(Hash) else [] end end
Source
# File plugins/huggingface.rb, line 111 def self.parse_command(text) stripped = text.to_s.strip return { action: :help } if stripped.empty? if (match = stripped.match(/\Alogin\s+(\S+)\z/i)) return { action: :login, token: match[1] } end return { action: :logout } if stripped.match?(/\Alogout\z/i) return { action: :whoami } if stripped.match?(/\A(?:whoami|status)\z/i) if stripped.match?(/\Ahardware\z/i) return { action: :hardware, mode: :show } end if stripped.match?(/\Ahardware\s+sync\z/i) return { action: :hardware, mode: :sync } end return { action: :recommend } if stripped.match?(/\Arecommend\z/i) %w[search censored uncensored].each do |verb| if (match = stripped.match(/\A#{verb}\s+(.+)\z/i)) query = match[1].to_s.strip return { action: :error, error: usage_message } if query.empty? moderation = case verb when "censored" then :censored when "uncensored" then :uncensored end return { action: :search, moderation: moderation, query: query } end return { action: :error, error: usage_message } if stripped.match?(/\A#{verb}\z/i) end { action: :error, error: usage_message } end
Source
# File plugins/huggingface.rb, line 213 def self.parse_compute_hardware(data) payload = data.is_a?(String) ? JSON.parse(data) : data rescue JSON::ParserError return empty_hardware else devices = extract_devices(payload) primary = devices.find { |device| device[:primary] } || devices.first || {} { primary_name: primary[:name], primary_memory_gb: primary[:memory_gb], device_count: devices.sum { |device| device[:count] || 1 }, devices: devices } end
Source
# File plugins/huggingface.rb, line 180 def self.parse_json(body) data = body.is_a?(String) ? JSON.parse(body) : body return data unless data.is_a?(Hash) && data["error"] raise StandardError, data["error"].to_s end
Source
# File plugins/huggingface.rb, line 332 def self.parse_memory_gb(value) return nil if value.nil? return value.to_f.round if value.is_a?(Numeric) str = value.to_s.strip return nil if str.empty? if (match = str.match(/\A(\d+(?:\.\d+)?)\s*GB\z/i)) return match[1].to_f.round end return str.to_i if str.match?(/\A\d+(?:\.\d+)?\z/) nil end
Source
# File plugins/huggingface.rb, line 187 def self.parse_whoami(data) payload = data.is_a?(String) ? JSON.parse(data) : data if payload["error"] return { success: false, error: "Hugging Face login failed — #{payload['error']}" } end username = payload["name"].to_s.strip return { success: false, error: "Hugging Face login failed — missing username" } if username.empty? { success: true, username: username, fullname: payload["fullname"].to_s.strip, type: payload["type"].to_s } rescue JSON::ParserError { success: false, error: "Invalid Hugging Face login response" } end
Source
# File plugins/huggingface.rb, line 107 def self.private_message?(channel) channel.nil? || channel.to_s.strip.empty? end
Source
# File plugins/huggingface.rb, line 89 def run_recommend(account:, fetch: nil) vram_gb = account.dig("hardware", "primary_memory_gb") token = account["token"].to_s sections = fetch_recommendations(vram_gb: vram_gb, token: token, fetch: fetch) format_recommend_reply(sections, hardware: account["hardware"]) end
Source
# File plugins/huggingface.rb, line 446 def self.run_search(query:, moderation:, account:, fetch: nil) vram_gb = account&.dig("hardware", "primary_memory_gb") token = account&.dig("token") models = search_models( query: query, moderation: moderation, vram_gb: vram_gb, token: token, fetch: fetch ) format_search_reply( models, query: query, moderation: moderation, hardware: account&.dig("hardware") ) end
Source
# File plugins/huggingface.rb, line 386 def self.save_account(network:, channel:, nick:, account:) RabbotDb.set_plugin_json( plugin: PLUGIN_NAME, key: ACCOUNT_KEY, value: account, network: network, channel: nil, nick: nick ) end
Source
# File plugins/huggingface.rb, line 67 def search_models(query:, moderation: nil, vram_gb: nil, token: nil, limit: DEFAULT_LIMIT, fetch: nil) fetcher = fetch || ->(url, token:) { api_get(url, token: token) } HuggingfaceModels.search_models( query: query, moderation: moderation, vram_gb: vram_gb, token: token, limit: limit, fetch: fetcher ) end
Source
# File plugins/huggingface.rb, line 427 def self.stringify_hardware(hardware) { "primary_name" => hardware[:primary_name], "primary_memory_gb" => hardware[:primary_memory_gb], "device_count" => hardware[:device_count] } end
Source
# File plugins/huggingface.rb, line 435 def self.sync_hardware(network:, channel:, nick:, fetch: nil) account = load_account(network: network, channel: channel, nick: nick) return { success: false, error: "Not logged in — use /msg <bot> !hf login <token> first." } unless account token = account["token"].to_s hardware = fetch_compute_hardware(token, fetch: fetch) account["hardware"] = stringify_hardware(hardware) save_account(network: network, channel: channel, nick: nick, account: account) { success: true, hardware: hardware } end
Source
# File plugins/huggingface.rb, line 349 def self.truthy?(value) value == true || value.to_s.strip.casecmp("true").zero? end
Source
# File plugins/huggingface.rb, line 54 def usage_message "Usage: /msg <bot> !hf login <token> | !hf logout | whoami | hardware [sync] | " \ "search <query> | censored <query> | uncensored <query> | recommend" end
Source
# File plugins/huggingface.rb, line 206 def self.verify_token(token, fetch: nil) body = api_get(WHOAMI_URL, token: token, fetch: fetch) parse_whoami(body) rescue StandardError => e { success: false, error: "Hugging Face login failed — #{e.message}" } end
Public Instance Methods
Source
# File plugins/huggingface.rb, line 549 def execute(m, args_text) network = bot.config.server channel = m.channel&.name nick = m.user.nick result = self.class.dispatch_command( args_text, network: network, nick: nick, channel: channel, ai_allowed: ai_allowed?(m), fetch: nil ) if result[:error] themed_flood_safe_reply(m, result[:error]) return end themed_flood_safe_reply(m, result[:reply]) if result[:reply] end