discourse-ai/lib/completions/cancel_manager.rb
Sam c34fcc8a95
FEATURE: forum researcher persona for deep research (#1313)
This commit introduces a new Forum Researcher persona specialized in deep forum content analysis along with comprehensive improvements to our AI infrastructure.

Key additions:

    New Forum Researcher persona with advanced filtering and analysis capabilities
    Robust filtering system supporting tags, categories, dates, users, and keywords
    LLM formatter to efficiently process and chunk research results

Infrastructure improvements:

    Implemented CancelManager class to centrally manage AI completion cancellations
    Replaced callback-based cancellation with a more robust pattern
    Added systematic cancellation monitoring with callbacks

Other improvements:

    Added configurable default_enabled flag to control which personas are enabled by default
    Updated translation strings for the new researcher functionality
    Added comprehensive specs for the new components

    Renames Researcher -> Web Researcher

This change makes our AI platform more stable while adding powerful research capabilities that can analyze forum trends and surface relevant content.
2025-05-14 12:36:16 +10:00

110 lines
3.0 KiB
Ruby

# frozen_string_literal: true
# special object that can be used to cancel completions and http requests
module DiscourseAi
module Completions
class CancelManager
attr_reader :cancelled
attr_reader :callbacks
def initialize
@cancelled = false
@callbacks = Concurrent::Array.new
@mutex = Mutex.new
@monitor_thread = nil
end
def monitor_thread
@mutex.synchronize { @monitor_thread }
end
def start_monitor(delay: 0.5, &block)
@mutex.synchronize do
raise "Already monitoring" if @monitor_thread
raise "Expected a block" if !block
db = RailsMultisite::ConnectionManagement.current_db
@stop_monitor = false
@monitor_thread =
Thread.new do
begin
loop do
done = false
@mutex.synchronize { done = true if @stop_monitor }
break if done
sleep delay
@mutex.synchronize { done = true if @stop_monitor }
@mutex.synchronize { done = true if cancelled? }
break if done
should_cancel = false
RailsMultisite::ConnectionManagement.with_connection(db) do
should_cancel = block.call
end
@mutex.synchronize { cancel! if should_cancel }
break if cancelled?
end
ensure
@mutex.synchronize { @monitor_thread = nil }
end
end
end
end
def stop_monitor
monitor_thread = nil
@mutex.synchronize { monitor_thread = @monitor_thread }
if monitor_thread
@mutex.synchronize { @stop_monitor = true }
# so we do not deadlock
monitor_thread.wakeup
monitor_thread.join(2)
# should not happen
if monitor_thread.alive?
Rails.logger.warn("DiscourseAI: CancelManager monitor thread did not stop in time")
monitor_thread.kill if monitor_thread.alive?
end
@monitor_thread = nil
end
end
def cancelled?
@cancelled
end
def add_callback(cb)
@callbacks << cb
end
def remove_callback(cb)
@callbacks.delete(cb)
end
def cancel!
@cancelled = true
monitor_thread = @monitor_thread
if monitor_thread && monitor_thread != Thread.current
monitor_thread.wakeup
monitor_thread.join(2)
if monitor_thread.alive?
Rails.logger.warn("DiscourseAI: CancelManager monitor thread did not stop in time")
monitor_thread.kill if monitor_thread.alive?
end
end
@callbacks.each do |cb|
begin
cb.call
rescue StandardError
# ignore cause this may have already been cancelled
end
end
end
end
end
end