Class: TokenReel::Timeline

Inherits:
Object
  • Object
show all
Defined in:
lib/token_reel/timeline.rb

Overview

Pure function of time -> State. Doesn't touch the filesystem or ImageMagick at all, which makes it cheap to sample as densely as we like when building the frame list.

Constant Summary collapse

2.0
DOT_INTERVAL =

seconds between "thinking..." dot ticks

0.35

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Timeline

Returns a new instance of Timeline.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/token_reel/timeline.rb', line 27

def initialize(config)
  @config = config
  @prompt_tokens = Tokenizer.tokenize(config.prompt, config.unit)
  @reasoning_tokens = Tokenizer.tokenize(config.reasoning, config.unit)
  @response_tokens = Tokenizer.tokenize(config.response, config.unit)
  @prompt_full = prompt_tokens.join
  @reasoning_full = reasoning_tokens.join
  @response_full = response_tokens.join

  @prompt_interval = config.prompt_tps.to_f.positive? ? 1.0 / config.prompt_tps : 0
  @reasoning_interval = config.reasoning_tps.to_f.positive? ? 1.0 / config.reasoning_tps : 1.0 / config.tps
  @response_interval = 1.0 / config.tps

  @prompt_done_t = @prompt_interval * prompt_tokens.size
  @thinking_end_t = prompt_done_t + config.ttft.to_f
  @reasoning_end_t = thinking_end_t + @reasoning_interval * reasoning_tokens.size
  @stream_end_t = reasoning_end_t + @response_interval * response_tokens.size
  @duration = stream_end_t + config.hold.to_f
end

Instance Attribute Details

#durationObject (readonly)

Returns the value of attribute duration.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def duration
  @duration
end

#prompt_done_tObject (readonly)

Returns the value of attribute prompt_done_t.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def prompt_done_t
  @prompt_done_t
end

#prompt_fullObject (readonly)

Returns the value of attribute prompt_full.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def prompt_full
  @prompt_full
end

#prompt_tokensObject (readonly)

Returns the value of attribute prompt_tokens.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def prompt_tokens
  @prompt_tokens
end

#reasoning_end_tObject (readonly)

Returns the value of attribute reasoning_end_t.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def reasoning_end_t
  @reasoning_end_t
end

#reasoning_fullObject (readonly)

Returns the value of attribute reasoning_full.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def reasoning_full
  @reasoning_full
end

#reasoning_tokensObject (readonly)

Returns the value of attribute reasoning_tokens.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def reasoning_tokens
  @reasoning_tokens
end

#response_fullObject (readonly)

Returns the value of attribute response_full.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def response_full
  @response_full
end

#response_tokensObject (readonly)

Returns the value of attribute response_tokens.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def response_tokens
  @response_tokens
end

#stream_end_tObject (readonly)

Returns the value of attribute stream_end_t.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def stream_end_t
  @stream_end_t
end

#thinking_end_tObject (readonly)

Returns the value of attribute thinking_end_t.



23
24
25
# File 'lib/token_reel/timeline.rb', line 23

def thinking_end_t
  @thinking_end_t
end

Instance Method Details

#final_stateObject

The fully-revealed, cursor-off state -- used to size the canvas so every frame in the GIF shares identical dimensions.



100
101
102
103
# File 'lib/token_reel/timeline.rb', line 100

def final_state
  State.new(phase: :done, prompt_text: prompt_full, reasoning_text: "", response_text: response_full,
             cursor_on: false, dot_count: 0)
end

#max_reasoning_stateObject

The reasoning phase in full, cursor off -- used alongside final_state to size the canvas, since the reasoning trace (shown only while streaming, then replaced by the response) can be taller than the finished response.



109
110
111
112
# File 'lib/token_reel/timeline.rb', line 109

def max_reasoning_state
  State.new(phase: :reasoning, prompt_text: prompt_full, reasoning_text: reasoning_full, response_text: "",
             cursor_on: false, dot_count: 0)
end

#state_at(t) ⇒ Object



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
# File 'lib/token_reel/timeline.rb', line 47

def state_at(t)
  t = t.clamp(0, duration)

  if t < prompt_done_t
    n = @prompt_interval.positive? ? (t / @prompt_interval).floor : prompt_tokens.size
    State.new(
      phase: :typing_prompt,
      prompt_text: prompt_tokens[0...n].join,
      reasoning_text: "",
      response_text: "",
      cursor_on: blink_on?(t),
      dot_count: 0
    )
  elsif t < thinking_end_t
    elapsed = t - prompt_done_t
    State.new(
      phase: :thinking,
      prompt_text: prompt_full,
      reasoning_text: "",
      response_text: "",
      cursor_on: blink_on?(t),
      dot_count: ((elapsed / DOT_INTERVAL).to_i % 4)
    )
  elsif t < reasoning_end_t
    elapsed = t - thinking_end_t
    n = @reasoning_interval.positive? ? (elapsed / @reasoning_interval).floor : reasoning_tokens.size
    n = n.clamp(0, reasoning_tokens.size)
    State.new(
      phase: :reasoning,
      prompt_text: prompt_full,
      reasoning_text: reasoning_tokens[0...n].join,
      response_text: "",
      cursor_on: blink_on?(t),
      dot_count: 0
    )
  else
    elapsed = t - reasoning_end_t
    n = @response_interval.positive? ? (elapsed / @response_interval).floor : response_tokens.size
    n = n.clamp(0, response_tokens.size)
    done = n >= response_tokens.size
    State.new(
      phase: done ? :done : :streaming,
      prompt_text: prompt_full,
      reasoning_text: "",
      response_text: response_tokens[0...n].join,
      cursor_on: blink_on?(t),
      dot_count: 0
    )
  end
end