Class: RubyRich::AppShell

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_rich/app_shell.rb

Direct Known Subclasses

AgentShell

Defined Under Namespace

Classes: FocusTarget, FramedView, HeaderView, StatusView

Constant Summary collapse

DEFAULT_COMMANDS =
[
  { label: "/help", value: "/help", description: "Show commands" },
  { label: "/plan", value: "/plan", description: "Append a plan note" },
  { label: "/thinking", value: "/thinking", description: "Add a thinking block" },
  { label: "/tool", value: "/tool", description: "Add a tool call" },
  { label: "/quit", value: "/quit", description: "Exit demo" }
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(title: "Agent", subtitle: nil, model: "deepseek-v4-pro", theme: Theme.agent_dark, commands: DEFAULT_COMMANDS, on_submit: nil) ⇒ AppShell

Returns a new instance of AppShell.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/ruby_rich/app_shell.rb', line 15

def initialize(title: "Agent", subtitle: nil, model: "deepseek-v4-pro", theme: Theme.agent_dark, commands: DEFAULT_COMMANDS, on_submit: nil)
  @title = title
  @subtitle = subtitle || "DeepSeek-TUI · #{model}"
  @model = model
  @theme = theme
  @on_submit = on_submit
  @status = "agent · #{model}"
  @token_usage = nil
  @progress_text = nil

  @transcript = Transcript.new
  @progress_manager = ProgressManager.new(on_change: ->(text) { @progress_text = text })
  @viewport = Viewport.new(@transcript, scrollbar: true, auto_scroll: true)
  @sidebar = Sidebar.new
  @composer = Composer.new(
    placeholder: "编写任务或使用 /。",
    commands: commands,
    on_submit: method(:handle_submit),
    on_select: method(:handle_select)
  )
  @focus_manager = FocusManager.new
  @layout = build_layout
  attach_components
end

Instance Attribute Details

#composerObject (readonly)

Returns the value of attribute composer.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def composer
  @composer
end

#focus_managerObject (readonly)

Returns the value of attribute focus_manager.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def focus_manager
  @focus_manager
end

#layoutObject (readonly)

Returns the value of attribute layout.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def layout
  @layout
end

#liveObject (readonly)

Returns the value of attribute live.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def live
  @live
end

#progress_managerObject (readonly)

Returns the value of attribute progress_manager.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def progress_manager
  @progress_manager
end

Returns the value of attribute sidebar.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def sidebar
  @sidebar
end

#themeObject (readonly)

Returns the value of attribute theme.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def theme
  @theme
end

#token_usageObject (readonly)

Returns the value of attribute token_usage.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def token_usage
  @token_usage
end

#transcriptObject (readonly)

Returns the value of attribute transcript.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def transcript
  @transcript
end

#viewportObject (readonly)

Returns the value of attribute viewport.



5
6
7
# File 'lib/ruby_rich/app_shell.rb', line 5

def viewport
  @viewport
end

Instance Method Details

#add_assistant(text) ⇒ Object



46
47
48
49
50
# File 'lib/ruby_rich/app_shell.rb', line 46

def add_assistant(text)
  @transcript.add_assistant(text)
  @viewport.scroll_to_bottom
  self
end

#add_diff(title: nil, content:, language: "diff") ⇒ Object



76
77
78
79
80
81
# File 'lib/ruby_rich/app_shell.rb', line 76

def add_diff(title: nil, content:, language: "diff")
  label = title ? "#{title}\n#{content}" : content
  @transcript.add_block(:diff, label, language: language)
  @viewport.scroll_to_bottom
  self
end

#add_markdown(text) ⇒ Object



70
71
72
73
74
# File 'lib/ruby_rich/app_shell.rb', line 70

def add_markdown(text)
  @transcript.add_markdown(text)
  @viewport.scroll_to_bottom
  self
end

#add_separator(label = nil) ⇒ Object



64
65
66
67
68
# File 'lib/ruby_rich/app_shell.rb', line 64

def add_separator(label = nil)
  @transcript.add_separator(label)
  @viewport.scroll_to_bottom
  self
end

#add_thinking(text, status: "idle", collapsed: true) ⇒ Object



52
53
54
55
56
# File 'lib/ruby_rich/app_shell.rb', line 52

def add_thinking(text, status: "idle", collapsed: true)
  @transcript.add_thinking(text, status: status, collapsed: collapsed)
  @viewport.scroll_to_bottom
  self
end

#add_tool(name, status: :running, result: nil, collapsed: false) ⇒ Object



58
59
60
61
62
# File 'lib/ruby_rich/app_shell.rb', line 58

def add_tool(name, status: :running, result: nil, collapsed: false)
  @transcript.add_tool(name, status: status, result: result, collapsed: collapsed)
  @viewport.scroll_to_bottom
  self
end

#add_user(text) ⇒ Object



40
41
42
43
44
# File 'lib/ruby_rich/app_shell.rb', line 40

def add_user(text)
  @transcript.add_user(text)
  @viewport.scroll_to_bottom
  self
end

#confirm(title:, message:, choices:, default: nil, &callback) ⇒ Object



119
120
121
122
123
# File 'lib/ruby_rich/app_shell.rb', line 119

def confirm(title:, message:, choices:, default: nil, &callback)
  result = default || choices.first&.fetch(:key)
  callback.call(result) if callback
  result
end

#form(title:, fields:, &callback) ⇒ Object



125
126
127
128
129
130
131
132
133
# File 'lib/ruby_rich/app_shell.rb', line 125

def form(title:, fields:, &callback)
  values = {}
  fields.each do |field|
    name = field.fetch(:name).to_sym
    values[name] = field.key?(:default) ? field[:default] : default_field_value(field)
  end
  callback.call(values) if callback
  values
end

#open_pager(text, command: ENV.fetch("PAGER", "less -R")) ⇒ Object



135
136
137
138
139
140
141
142
# File 'lib/ruby_rich/app_shell.rb', line 135

def open_pager(text, command: ENV.fetch("PAGER", "less -R"))
  Terminal.with_cooked(mouse: true) do
    IO.popen(command, "w") { |io| io.write(text.to_s) }
  end
  true
rescue
  false
end

#set_tasks(tasks) ⇒ Object



88
89
90
91
# File 'lib/ruby_rich/app_shell.rb', line 88

def set_tasks(tasks)
  @sidebar.set_tasks(tasks)
  self
end

#show_token_usage(input: nil, output: nil, total: nil, **extra) ⇒ Object



102
103
104
105
# File 'lib/ruby_rich/app_shell.rb', line 102

def show_token_usage(input: nil, output: nil, total: nil, **extra)
  @token_usage = { input: input, output: output, total: total }.merge(extra).compact
  self
end

#start(refresh_rate: 24, mouse: true, alt_screen: false) ⇒ Object



144
145
146
147
148
149
150
151
# File 'lib/ruby_rich/app_shell.rb', line 144

def start(refresh_rate: 24, mouse: true, alt_screen: false)
  Live.start(@layout, refresh_rate: refresh_rate, mouse: mouse, alt_screen: alt_screen, autowrap: false) do |live|
    @live = live
    live.listening = true
  end
ensure
  @live = nil
end

#start_progress(message = nil, owner: Thread.current.object_id, style: :primary, quiet_on_fast_finish: false) ⇒ Object



107
108
109
110
111
# File 'lib/ruby_rich/app_shell.rb', line 107

def start_progress(message = nil, owner: Thread.current.object_id, style: :primary, quiet_on_fast_finish: false)
  _ = style
  _ = quiet_on_fast_finish
  @progress_manager.start(message, owner: owner)
end

#status=(text) ⇒ Object



93
94
95
# File 'lib/ruby_rich/app_shell.rb', line 93

def status=(text)
  @status = text.to_s
end

#stopObject



153
154
155
156
157
# File 'lib/ruby_rich/app_shell.rb', line 153

def stop
  return false unless @live

  @live.post { |live| live.stop } || false
end

#update_plan(text) ⇒ Object



83
84
85
86
# File 'lib/ruby_rich/app_shell.rb', line 83

def update_plan(text)
  @sidebar.update_plan(text)
  self
end

#update_status(text) ⇒ Object



97
98
99
100
# File 'lib/ruby_rich/app_shell.rb', line 97

def update_status(text)
  self.status = text
  self
end

#with_progress(message = nil, style: :primary, quiet_on_fast_finish: false, &block) ⇒ Object



113
114
115
116
117
# File 'lib/ruby_rich/app_shell.rb', line 113

def with_progress(message = nil, style: :primary, quiet_on_fast_finish: false, &block)
  _ = style
  _ = quiet_on_fast_finish
  @progress_manager.with_progress(message, &block)
end