summaryrefslogtreecommitdiff
path: root/controllers/session.rb
blob: 3d5d7073d9d58b4dbde5cb7424544c16d9a8b2a2 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# frozen_string_literal: true

require 'babosa'

require 'fileutils'

require_relative '../logic/helpers/hash'
require_relative '../logic/cartridge/streaming'
require_relative '../logic/cartridge/interaction'
require_relative '../components/storage'
require_relative '../components/adapter'

module NanoBot
  module Controllers
    STREAM_TIMEOUT_IN_SECONDS = 5

    class Session
      attr_accessor :stream

      def initialize(provider:, cartridge:, state: nil, stream: $stdout)
        @stream = stream
        @provider = provider
        @cartridge = cartridge

        @stateless = state.nil? || state.strip == '-' || state.strip.empty?

        if @stateless
          @state = { history: [] }
        else
          @state_path = Components::Storage.build_path_and_ensure_state_file!(
            state.strip, @cartridge
          )
          @state = load_state
        end
      end

      def state
        { state: { path: @state_path, content: @state } }
      end

      def load_state
        @state = Logic::Helpers::Hash.symbolize_keys(JSON.parse(File.read(@state_path)))
      end

      def store_state!
        File.write(@state_path, JSON.generate(@state))
      end

      def boot(mode:)
        return unless Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors boot instruction])

        behavior = Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors boot]) || {}

        input = { behavior:, history: [] }

        process(input, mode:)
      end

      def evaluate_and_print(message, mode:)
        behavior = Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors interaction]) || {}

        @state[:history] << {
          who: 'user',
          message: Components::Adapter.apply(
            :input, Logic::Cartridge::Interaction.input(@cartridge, mode.to_sym, message)
          )
        }

        input = { behavior:, history: @state[:history] }

        process(input, mode:)
      end

      def process(input, mode:)
        interface = Logic::Helpers::Hash.fetch(@cartridge, [:interfaces, mode.to_sym]) || {}

        streaming = Logic::Cartridge::Streaming.enabled?(@cartridge, mode.to_sym)

        input[:interface] = interface

        updated_at = Time.now

        ready = false
        @provider.evaluate(input) do |output, finished|
          updated_at = Time.now

          if finished
            @state[:history] << Marshal.load(Marshal.dump(output))

            output = Logic::Cartridge::Interaction.output(
              @cartridge, mode.to_sym, output, streaming, finished
            )

            output[:message] = Components::Adapter.apply(:output, output[:message])

            self.print(output[:message]) unless streaming

            ready = true
            flush
          elsif streaming
            self.print(output[:message])
          end
        end

        until ready
          seconds = (Time.now - updated_at).to_i
          raise StandardError, 'The stream has become unresponsive.' if seconds >= STREAM_TIMEOUT_IN_SECONDS
        end

        store_state! unless @stateless
      end

      def flush
        @stream.flush
      end

      def print(content)
        @stream.write(content)
      end
    end
  end
end