# frozen_string_literal: true # WikiClient — fetches the Vodafone Wiki.js content via its GraphQL API for the RAG index. # JWT from env (WIKI_JWT). Strips HTML to plain text. Excludes pure data-dump pages (inventory / # location tables) that are noise for call summaries. Standard library only; transport injectable for tests. require "json" require "net/http" require "uri" module Helpdesk class WikiClient DEFAULT_BASE = "https://wikivf.max.support" # exclude data dumps: path keywords + an oversize guard (the standards pages ~145K chars stay; # the 511K PSU-inventory and 203K location-table pages go). EXCLUDE_PATHS = [/inventura/i, /invent/i, /lokalit/i].freeze MAX_PAGE_CHARS = 160_000 def initialize(base_url: DEFAULT_BASE, jwt: ENV["WIKI_JWT"], transport: nil) @base = base_url.chomp("/") @jwt = jwt @transport = transport # ->(query_string) { response_body_json } — for tests end # → [{ id:, path:, title:, updated_at: }] def list d = gql("{ pages { list { id path title updatedAt } } }") d.dig("data", "pages", "list").map { |p| { id: p["id"], path: p["path"], title: p["title"], updated_at: p["updatedAt"] } } end # → plain text of one page (HTML stripped), or nil if forbidden/missing. def content(id) d = gql("{ pages { single(id:#{id}){ content } } }") html = d.dig("data", "pages", "single", "content") html && html_to_text(html) end # The knowledge pages to index: [{ title:, path:, text: }], excluding data dumps + empties. def knowledge_pages(exclude_paths: EXCLUDE_PATHS, max_chars: MAX_PAGE_CHARS) list.reject { |p| exclude_paths.any? { |re| p[:path] =~ re } }.filter_map do |p| text = content(p[:id]) next if text.nil? || text.strip.empty? || text.length > max_chars { title: p[:title], path: p[:path], text: text } end end private def gql(query) raw = @transport ? @transport.call(query) : post(query) JSON.parse(raw) end def post(query) uri = URI("#{@base}/graphql") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == "https" http.open_timeout = 15 http.read_timeout = 60 req = Net::HTTP::Post.new(uri) req["Authorization"] = "Bearer #{@jwt}" req["Content-Type"] = "application/json" req.body = JSON.generate(query: query) http.request(req).body end # minimal HTML → text: drop script/style, tags → spaces, decode a few entities, collapse whitespace. def html_to_text(html) t = html.dup t.gsub!(%r{<(script|style)[^>]*>.*?}mi, " ") t.gsub!(/<[^>]+>/, " ") { " " => " ", "&" => "&", "<" => "<", ">" => ">", """ => '"', "'" => "'" } .each { |k, v| t.gsub!(k, v) } t.gsub(/\s+/, " ").strip end end end