Module: OllamaChat::PersonaeManagement

Included in:
Chat
Defined in:
lib/ollama_chat/personae_management.rb

Overview

Module for managing personas in Ollama chat application

This module provides functionality to manage persona files, including creating, reading, updating, and deleting persona definitions stored as Markdown files in the personae directory.

Instance Method Summary collapse

Instance Method Details

#add_personaString (private)

Creates a new persona file interactively.

The method prompts the user to enter a name for the persona, creates an empty Markdown file with that name in the personas directory (if it does not already exist), opens the file in the configured editor, and finally returns the result of calling #personae_result on the created file.

Returns:

  • (String)

    The JSON string returned by #personae_result, or nil if the user cancels.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ollama_chat/personae_management.rb', line 128

def add_persona
  persona_name = ask?(
    prompt: "❓ Enter the name of the new persona (or press return to cancel): "
  ).full? or return

  pathname = personae_directory + "#{persona_name}.md"

  unless pathname.exist?
    File.write pathname, config.prompts.persona
  end

  edit_file(pathname)

  personae_result(pathname.basename)
end

#available_personaeArray<String> (private)

Returns a sorted list of available persona file names.

This method scans the personae directory for Markdown files and returns their basenames sorted alphabetically.

Returns:

  • (Array<String>)

    Sorted array of persona filenames without extension



115
116
117
# File 'lib/ollama_chat/personae_management.rb', line 115

def available_personae
  personae_directory.glob('*.md').map(&:basename).sort
end

#choose_persona(chosen: nil) ⇒ String? (private)

Interactive method to select a persona from a list.

Allows the user to choose a persona from available options or exit. Selected persona is returned if successful, nil otherwise.

Parameters:

  • chosen (Set, nil) (defaults to: nil)

    Optional set of already selected personas

Returns:

  • (String, nil)

    The selected persona name or nil if user exits



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/ollama_chat/personae_management.rb', line 249

def choose_persona(chosen: nil)
  personae_list = available_personae.reject { chosen&.member?(_1) }
  if personae_list.empty?
    STDERR.puts "No personae defined."
    return
  end
  personae_list.unshift('[EXIT]')
  case chosen = OllamaChat::Utils::Chooser.choose(personae_list)
  when '[EXIT]', nil
    STDOUT.puts "Exiting chooser."
    return
  else
    chosen
  end
end

#default_persona_nameString (private)

The default_persona_name method returns the name of the currently set default persona by extracting its basename and removing the file extension, unless no persona is set.

Returns:

  • (String)

    the default persona name or nil if none.



64
65
66
67
68
# File 'lib/ollama_chat/personae_management.rb', line 64

def default_persona_name
  if @default_persona.present? && @default_persona != :none
    @default_persona.basename.sub_ext('').to_s
  end
end

#delete_personaString (private)

Interactive method to delete an existing persona with backup functionality.

Prompts the user to select a persona, asks for confirmation, and creates a timestamped backup of the persona file before deletion.

Returns:

  • (String)

    Returns a JSON object with deletion status on success, or nil if no persona was selected or deletion was cancelled



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/ollama_chat/personae_management.rb', line 167

def delete_persona
  if persona = choose_persona
    persona         = persona
    pathname        = personae_directory + persona
    backup_pathname = persona_backup_pathname(persona)
    if pathname.exist?
      STDOUT.puts "Deleting '#{bold{persona.sub_ext('')}}'..."
      STDOUT.puts "Backup will be saved to: #{backup_pathname}"

      if confirm?(prompt: "🔔 Are you sure? (y/n) ") =~ /y/i
        FileUtils.mv pathname, backup_pathname
        STDOUT.puts "✅ Persona #{bold{persona.sub_ext('')}} deleted successfully"
        {
          success: true,
          persona: persona.sub_ext(''),
          backup_pathname:,
        }.to_json
      else
        STDOUT.puts "Deletion cancelled."
        return
      end
    else
      STDOUT.puts "Persona not found."
      return
    end
  end
end

#edit_personaObject (private)

Interactive method to edit an existing persona file.

Prompts the user to select a persona, opens it for editing, backups the old content, and returns the result after editing.



199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/ollama_chat/personae_management.rb', line 199

def edit_persona
  if persona = choose_persona
    persona  = persona
    pathname = personae_directory + persona
    old_content = File.read(pathname)
    if edit_file(pathname)
      changed = File.read(pathname) != old_content
      if changed
        File.write(persona_backup_pathname(persona), old_content)
      end
      personae_result(persona)
    end
  end
end

#info_personaObject (private)

Displays detailed information about a selected persona.

Shows the persona’s profile using kramdown formatting with ansi parsing.



217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/ollama_chat/personae_management.rb', line 217

def info_persona
  if persona = choose_persona
    _persona, persona_profile = load_persona_file(persona)
    use_pager do |output|
      output.puts kramdown_ansi_parse(<<~EOT)
        # Persona #{persona.sub_ext('')}
        ---
        #{persona_profile}
        ---
      EOT
    end
  end
end

#list_personaeObject (private)

Lists all available persona names.

Outputs the sorted list of persona filenames to STDOUT.



234
235
236
237
238
239
240
# File 'lib/ollama_chat/personae_management.rb', line 234

def list_personae
  if personae = available_personae.full?
    STDOUT.puts available_personae
  else
    STDOUT.puts "No personae defined."
  end
end

#load_persona_file(persona) ⇒ Array<Pathname, String> (private)

Loads a persona file from disk.

Parameters:

  • persona (String)

    The basename of the persona (without extension)

Returns:

  • (Array<Pathname, String>)

    Returns the pathname and its content as a string



312
313
314
315
316
317
# File 'lib/ollama_chat/personae_management.rb', line 312

def load_persona_file(persona)
  pathname = personae_directory + persona
  if pathname.exist?
    return pathname, pathname.read
  end
end

#load_personaeObject (private)

Interactive method to load multiple personae for use.

Allows sequential selection of multiple personae. Returns JSON results for each loaded persona.



269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/ollama_chat/personae_management.rb', line 269

def load_personae
  chosen = Set[]
  while persona = choose_persona(chosen: chosen)
    chosen << persona
  end

  if chosen.empty?
    STDOUT.puts "No persona loaded."
    return
  end

  personae_result(chosen)
end

#persona_backup_pathname(persona) ⇒ Pathname (private)

Note:

The timestamp ensures each backup has a unique filename when multiple backups of the same persona exist

Generates the backup pathname for a persona file with timestamp.

Creates a unique backup filename with the persona name, timestamp, and .md.bak extension. The timestamp format is YYYYMMDDHHMMSS for precise identification of backup versions.

Parameters:

  • persona (String)

    The persona name to create a backup path for

Returns:

  • (Pathname)

    The full path to the backup file



155
156
157
158
# File 'lib/ollama_chat/personae_management.rb', line 155

def persona_backup_pathname(persona)
  timestamp = Time.now.strftime('%Y%m%d%H%M%S')
  personae_backup_directory + (persona.sub_ext('').to_s + ?- + timestamp + '.md.bak')
end

#personae_backup_directoryPathname (private)

Note:

The directory is created automatically if it doesn’t exist

Returns the directory path for persona backup files.

Returns:

  • (Pathname)

    Path to the backups subdirectory within personae directory



26
27
28
# File 'lib/ollama_chat/personae_management.rb', line 26

def personae_backup_directory
  personae_directory + 'backups'
end

#personae_directoryPathname (private)

Note:

The directory is created automatically if it doesn’t exist

Returns the directory path where persona files are stored.

The path is constructed using the XDG_CONFIG_HOME environment variable for platform-agnostic configuration storage.

Returns:

  • (Pathname)

    Path to the personae directory



17
18
19
# File 'lib/ollama_chat/personae_management.rb', line 17

def personae_directory
  OC::XDG_CONFIG_HOME + 'personae'
end

#personae_result(personae) ⇒ String (private)

Returns a JSON hash with results for one or more personae.

Loads the profile file for each persona and returns the results as JSON.

Parameters:

  • personae (String, Array<String>)

    Persona name(s) to load

Returns:

  • (String)

    JSON string containing persona profile information



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/ollama_chat/personae_management.rb', line 289

def personae_result(personae)
  personae = Array(personae)

  result = {}

  personae.each do |persona|
    pathname = personae_directory + persona
    pathname.exist? or next
    result[persona.sub_ext('')] = {
      pathname: ,
      profile:  pathname.read,
    }
  end

  result.to_json
end

#play_persona(pathname: nil) ⇒ Object (private)

Initiates roleplay with a selected persona.

Uses the persona selection and loading methods to generate the appropriate roleplay prompt.



337
338
339
340
341
# File 'lib/ollama_chat/personae_management.rb', line 337

def play_persona(pathname: nil)
  persona                  = choose_persona or return
  persona, persona_profile = load_persona_file(persona)
  play_persona_prompt(persona:, persona_profile:)
end

#play_persona_file(pathname) ⇒ Object (private)

Initiates roleplay with a persona from a specific file path.

Uses the pathname to identify the persona, reads its content, and generates the roleplay prompt.

Parameters:

  • pathname (String, Pathname)

    The path to the persona file



349
350
351
352
353
# File 'lib/ollama_chat/personae_management.rb', line 349

def play_persona_file(pathname)
  persona         = Pathname.new(pathname)
  persona_profile = pathname.read
  play_persona_prompt(persona:, persona_profile:)
end

#play_persona_prompt(persona:, persona_profile:) ⇒ String (private)

Generates the roleplay prompt string for a persona.

Creates a formatted prompt string that includes the persona name and profile.

Parameters:

  • persona (String)

    The persona name to include in the prompt

  • persona_profile (String)

    The persona profile content

Returns:

  • (String)

    Formatted roleplay prompt



326
327
328
329
330
331
# File 'lib/ollama_chat/personae_management.rb', line 326

def play_persona_prompt(persona:, persona_profile:)
  persona_name = persona.basename.sub_ext('')
  "Roleplay as persona %{persona_name} loaded from %{persona}\n\n%{persona_profile}" % {
    persona_name:, persona:, persona_profile:
  }
end

#reload_default_personaObject (private)

Reloads the default persona file if one is set and not :none, prompting the user for confirmation before playing the persona file or loading a new default persona and changing the default.



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
# File 'lib/ollama_chat/personae_management.rb', line 73

def reload_default_persona
  1.times do
    options = []
    if name = default_persona_name
      options << SearchUI::Wrapper.new(
        'reload_default',
        display: "Reload current default persona (#{name})"
      )
    end
    options << SearchUI::Wrapper.new(
      'keep',
      display: "Keep it as‑is – do not load a persona"
    )
    options << SearchUI::Wrapper.new(
      'choose_different',
      display: "Choose a different persona to become the new default and load it"
    )

    choice = OllamaChat::Utils::Chooser.choose(options)

    case choice&.value
    when nil, 'keep'
    when 'reload_default'
      return play_persona_file(@default_persona)
    when 'choose_different'
      if persona = choose_persona
        @default_persona = personae_directory + persona
        return play_persona_file(@default_persona)
      else
        redo
      end
    end
  end
  nil
end

#setup_persona_from_optsObject (private)

The setup_persona_from_opts method initializes persona setup by checking for a provided persona option, determining the appropriate file path, and playing the persona file if it exists.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/ollama_chat/personae_management.rb', line 41

def setup_persona_from_opts
  @default_persona and return
  @opts[?c] and return
  if persona = @opts[?p].full? { Pathname.new(_1) }
    if persona.extname == '.md'
      pathname = persona
    else
      pathname = personae_directory + (persona.to_s + '.md')
    end
    if pathname.exist?
      @default_persona = pathname
      play_persona_file pathname
    end
  end
ensure
  @default_persona ||= :none
end

#setup_personae_directoryObject (private)

Creates the personae directory structure if it doesn’t already exist.

This method ensures both the main personae directory and the backups subdirectory exist for proper file organization.



34
35
36
# File 'lib/ollama_chat/personae_management.rb', line 34

def setup_personae_directory
  FileUtils.mkdir_p personae_backup_directory
end