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 Attribute Summary collapse
-
#default_persona_name ⇒ String?
readonly
private
The default_persona_name method returns the name of the default persona.
Instance Method Summary collapse
-
#add_persona ⇒ Object
private
Creates a new persona file interactively.
-
#ask_to_set_default_persona_name(persona_name) ⇒ Boolean
private
Interactively asks the user if they want to set the specified persona as the current default for the session.
-
#assistant ⇒ String, ...
Returns the name of the current default persona.
-
#available_personae ⇒ Array<String>
private
Returns a sorted list of available persona file names.
-
#available_personae_names ⇒ Array<SearchUI::Wrapper>
private
Retrieves a list of available personas, decorated with their favourite status.
-
#backup_persona ⇒ Object
private
Backs up the content of a selected persona file.
-
#choose_persona(chosen: nil, none: false) ⇒ String, ...
private
Interactive method to select a persona from a list.
-
#convert_json_character_to_markdown(character) ⇒ String
private
Transforms raw character data (JSON or YAML) into a high-fidelity, structured Markdown persona profile using the persona architect prompt and the current persona template.
-
#default_persona ⇒ Pathname?
private
The default_persona method returns the path to the default persona file.
-
#default_persona_profile ⇒ String?
Retrieves the formatted roleplay prompt for the current default persona.
-
#delete_persona ⇒ String
private
Interactive method to delete an existing persona with backup functionality.
-
#determine_valid_new_name_for_persona(action) ⇒ String?
private
Interactively determines a valid, non-conflicting name for a new persona.
-
#duplicate_persona ⇒ self?
private
Interactively duplicates an existing persona profile to a new name.
-
#edit_persona ⇒ String?
private
Interactive method to edit an existing persona file.
-
#export_persona ⇒ self?
private
Interactively exports a persona profile to a specified file.
-
#import_persona(pathname) ⇒ String?
private
Imports a persona from a Markdown file, prompting for a new name.
-
#info_persona ⇒ Object
private
Displays detailed information about a selected persona.
-
#initial_persona_name ⇒ String?
The initial_persona_name method retrieves the initial persona for the chat session.
-
#list_personae(output: STDOUT) ⇒ Object
private
Lists all available persona names in a formatted table.
-
#load_persona_file(persona) ⇒ Array<Pathname, String>
private
Loads a persona file from disk.
-
#load_personae ⇒ Object
private
Interactive method to load multiple personae for use.
-
#pathname_to_persona_name(pathname) ⇒ String
private
Converts a persona pathname to its prompt name.
-
#persona_backup_pathname(persona) ⇒ Pathname
private
Generates the backup pathname for a persona file with timestamp.
-
#persona_description(persona, substitute_variables: false) ⇒ String?
private
Generates a formatted description of a persona, including its path and profile.
-
#persona_name_to_pathname(persona_name) ⇒ Pathname
private
Converts a persona prompt name to its full filesystem pathname.
-
#persona_name_with_favourite(name, favourited) ⇒ SearchUI::Wrapper
private
Helper to wrap a persona name with its favourite status for the UI.
-
#personae_backup_directory ⇒ Pathname
private
Returns the directory path for persona backup files.
-
#personae_directory ⇒ Pathname
private
Returns the directory path where persona files are stored.
-
#personae_result(personae) ⇒ String?
private
Compiles the descriptions for one or more personae into a single string.
-
#play_persona(persona) ⇒ String
private
Generates the roleplay prompt string for a persona.
-
#select_persona_path ⇒ String?
private
Prompts the user to select a persona, copies its filesystem path to the clipboard, and sets it as the prefill prompt for the next interaction.
-
#set_default_persona ⇒ String?
private
Interactively selects a persona and sets it as the default for the session.
-
#set_default_persona_name(persona_name) ⇒ String?
private
Sets the default persona name and updates the session.
-
#setup_persona_from_session ⇒ Pathname, ...
private
Initializes the default persona for the current chat session.
-
#setup_personae_directory ⇒ Object
private
Creates the personae directory structure if it doesn't already exist.
-
#substitute_variables(profile) ⇒ String
private
The substitute_variables method handles the substitution of variables in profiles.
Instance Attribute Details
#default_persona_name ⇒ String? (readonly, private)
The default_persona_name method returns the name of the default persona.
102 103 104 |
# File 'lib/ollama_chat/personae_management.rb', line 102 def default_persona_name @default_persona_name end |
Instance Method Details
#add_persona ⇒ Object (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.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ollama_chat/personae_management.rb', line 178 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, prompt(:persona).to_s end edit_file(pathname) nil end |
#ask_to_set_default_persona_name(persona_name) ⇒ Boolean (private)
Interactively asks the user if they want to set the specified persona as the current default for the session.
If the user confirms, the default persona is updated via
set_default_persona_name.
609 610 611 612 613 614 615 616 617 618 619 620 |
# File 'lib/ollama_chat/personae_management.rb', line 609 def ask_to_set_default_persona_name(persona_name) yes = confirm?( prompt: "🔔 Set the new persona promt as current default persona? (y/n) ", yes: /\Ay/i ) if yes set_default_persona_name(persona_name) true else false end end |
#assistant ⇒ String, ...
Returns the name of the current default persona.
If the default persona is set to :none or is not configured, it returns nil.
31 32 33 34 35 |
# File 'lib/ollama_chat/personae_management.rb', line 31 def assistant if default_persona_name && default_persona_name != :none default_persona_name end end |
#available_personae ⇒ Array<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.
144 145 146 |
# File 'lib/ollama_chat/personae_management.rb', line 144 def available_personae personae_directory.glob('*.md').map { pathname_to_persona_name(_1) } end |
#available_personae_names ⇒ Array<SearchUI::Wrapper> (private)
Retrieves a list of available personas, decorated with their favourite status.
164 165 166 167 168 169 170 |
# File 'lib/ollama_chat/personae_management.rb', line 164 def available_personae_names favs = all_favourited('persona') personae_directory.glob('*.md').map(&:basename).sort.map { |bn| persona_name = bn.sub_ext('').to_s persona_name_with_favourite(persona_name, favs[persona_name]) } end |
#backup_persona ⇒ Object (private)
Backs up the content of a selected persona file.
Prompts the user to select a persona from the available list. If a persona
is selected, its current content is read and saved to a designated backup
location using File.write. This ensures a safe copy is preserved before
any modifications are made to the original file.
281 282 283 284 285 286 287 288 289 |
# File 'lib/ollama_chat/personae_management.rb', line 281 def backup_persona if persona = choose_persona pathname = persona_name_to_pathname(persona) old_content = pathname.read backup_pathname = persona_backup_pathname(persona) backup_pathname.write(old_content) STDOUT.puts "Wrote backup of #{persona.to_s} to #{backup_pathname.to_s.inspect}." end end |
#choose_persona(chosen: nil, none: false) ⇒ 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 if user exits.
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# File 'lib/ollama_chat/personae_management.rb', line 379 def choose_persona(chosen: nil, none: false) personae_list = available_personae_names. reject { chosen&.member?(_1) } if personae_list.empty? STDERR.puts "No personae defined." return end personae_list.unshift('[NONE]') if none personae_list.unshift('[EXIT]') case persona = choose_entry(personae_list) when '[EXIT]', nil STDOUT.puts "Exiting chooser." return when '[NONE]' :none else persona.value end end |
#convert_json_character_to_markdown(character) ⇒ String (private)
Transforms raw character data (JSON or YAML) into a high-fidelity, structured Markdown persona profile using the persona architect prompt and the current persona template.
This method leverages the LLM to interpret raw attributes and expand them into evocative prose, ensuring the final output conforms to the system's standard persona structure. It also normalizes placeholder syntax to ensure compatibility with the internal persona system.
566 567 568 569 570 571 572 573 |
# File 'lib/ollama_chat/personae_management.rb', line 566 def convert_json_character_to_markdown(character) generate( prompt: prompt(:persona_architect).to_s % { character:, persona_template: prompt(:persona).to_s } ).response.gsub('{{user}}', '%{user}') end |
#default_persona ⇒ Pathname? (private)
The default_persona method returns the path to the default persona file.
108 109 110 111 112 |
# File 'lib/ollama_chat/personae_management.rb', line 108 def default_persona if default_persona_name && default_persona_name != :none personae_directory.join(default_persona_name).sub_ext('.md') end end |
#default_persona_profile ⇒ String?
Retrieves the formatted roleplay prompt for the current default persona.
18 19 20 21 22 |
# File 'lib/ollama_chat/personae_management.rb', line 18 def default_persona_profile if persona = default_persona and persona.exist? play_persona(persona) end end |
#delete_persona ⇒ String (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.
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/ollama_chat/personae_management.rb', line 216 def delete_persona if persona = choose_persona pathname = persona_name_to_pathname(persona) backup_pathname = persona_backup_pathname(persona) if pathname.exist? STDOUT.puts "Deleting '#{bold{persona}}'..." STDOUT.puts "Backup will be saved to: #{backup_pathname}" if confirm?(prompt: "🔔 Are you sure? (y/n) ", yes: /\Ay/i) FileUtils.mv pathname, backup_pathname default_persona_name == persona and set_default_persona_name(:none) STDOUT.puts "Persona #{bold{persona}} deleted successfully" self else STDOUT.puts "Deletion cancelled." return end else STDOUT.puts "Persona not found." return end end end |
#determine_valid_new_name_for_persona(action) ⇒ String? (private)
Interactively determines a valid, non-conflicting name for a new persona.
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 |
# File 'lib/ollama_chat/personae_management.rb', line 505 def determine_valid_new_name_for_persona(action) persona_name = nil loop do persona_name = ask?( prompt: "❓ Enter new persona prompt name #{action}, C-c ⇒ cancel: " ) if persona_name.nil? STDOUT.puts "Cancelled." return nil end if persona_name_to_pathname(persona_name).exist? STDOUT.puts "Persona prompt named #{bold{persona_name}} already exists." else break end end persona_name end |
#duplicate_persona ⇒ self? (private)
Interactively duplicates an existing persona profile to a new name.
The process follows these steps:
- Prompts the user to select a source persona via
choose_persona. - Prompts the user to enter a unique name for the duplicate via
determine_valid_new_name_for_persona. - Copies the content from the source persona file to the new persona file.
534 535 536 537 538 539 540 541 |
# File 'lib/ollama_chat/personae_management.rb', line 534 def duplicate_persona persona = choose_persona or return pathname = persona_name_to_pathname(persona) new_persona_name = determine_valid_new_name_for_persona('to ducplicate as') or return new_pathname = persona_name_to_pathname(new_persona_name) new_pathname.write(pathname.read) self end |
#edit_persona ⇒ String? (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.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/ollama_chat/personae_management.rb', line 247 def edit_persona if persona = choose_persona pathname = persona_name_to_pathname(persona) old_content = pathname.read if edit_file(pathname) changed = pathname.read != old_content if changed persona_backup_pathname(persona).write(old_content) ask_to_set_default_persona_name(persona) end persona end end end |
#export_persona ⇒ self? (private)
Interactively exports a persona profile to a specified file.
The process follows these steps:
- Prompts the user to select a persona via
choose_persona. - Displays the persona's current content to the terminal.
- Prompts for a destination filename via
determine_valid_output_filename. - Writes the persona content to the chosen file.
586 587 588 589 590 591 592 593 594 595 596 597 |
# File 'lib/ollama_chat/personae_management.rb', line 586 def export_persona persona = choose_persona or return pathname = persona_name_to_pathname(persona) content = pathname.read STDOUT.puts kramdown_ansi_parse( content + "\n---" ) filename = determine_valid_output_filename('to write to') or return filename.write(content) STDOUT.puts "Persona #{persona.inspect} was exported as #{filename.to_path.inspect}?" self end |
#import_persona(pathname) ⇒ String? (private)
Imports a persona from a Markdown file, prompting for a new name.
547 548 549 550 551 552 553 |
# File 'lib/ollama_chat/personae_management.rb', line 547 def import_persona(pathname) content = pathname.read persona_name = determine_valid_new_name_for_persona('to import') or return persona_pathname = persona_name_to_pathname(persona_name) persona_pathname.write(content) persona_name end |
#info_persona ⇒ Object (private)
Displays detailed information about a selected persona.
Shows the persona's profile using kramdown formatting with ansi parsing.
319 320 321 322 323 324 325 326 |
# File 'lib/ollama_chat/personae_management.rb', line 319 def info_persona if persona = choose_persona description = persona_description(persona) or return use_pager do |output| output.puts kramdown_ansi_parse(description) end end end |
#initial_persona_name ⇒ String?
The initial_persona_name method retrieves the initial persona for the chat session.
11 12 13 |
# File 'lib/ollama_chat/personae_management.rb', line 11 def initial_persona_name session&.default_persona_name end |
#list_personae(output: STDOUT) ⇒ Object (private)
Lists all available persona names in a formatted table.
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/ollama_chat/personae_management.rb', line 331 def list_personae(output: STDOUT) use_pager do |output| personae = available_personae if personae.empty? STDOUT.puts "No personae defined." return end favs = all_favourited('persona') table = Terminal::Table.new table.style = { all_separators: true, border: :unicode_round, } table.headings = %w[ NAME SIZE #TOK ].map { |header| bold { header } } personae.map do |persona_name| pathname = persona_name_to_pathname(persona_name) pathname.exist? or next [ pathname, pathname.size ] end.compact.sort_by(&:last).reverse_each do |pathname, size_bytes| persona_name = pathname.basename.sub_ext('').to_s size_bytes = pathname.size size = format_bytes(size_bytes) tokens = OllamaChat::Utils::TokenEstimator.estimate(size_bytes) tokens_size = format_tokens(tokens) is_default = default_persona_name == persona_name display_name = prefix_favourite(is_default ? bold { persona_name } : persona_name, favs[persona_name]) table << [ display_name, size, tokens_size, ] end table.align_column 1, :right table.align_column 2, :right output.puts table end end |
#load_persona_file(persona) ⇒ Array<Pathname, String> (private)
Loads a persona file from disk.
446 447 448 449 450 451 |
# File 'lib/ollama_chat/personae_management.rb', line 446 def load_persona_file(persona) pathname = persona_name_to_pathname(persona) if pathname.exist? return pathname, pathname.read end end |
#load_personae ⇒ Object (private)
Interactive method to load multiple personae for use.
Allows sequential selection of multiple personae. Returns JSON results for each loaded persona.
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/ollama_chat/personae_management.rb', line 403 def load_personae chosen = Set[] choose_with_state do while persona = choose_persona(chosen: chosen) persona == :none and next chosen << persona end end if chosen.empty? STDOUT.puts "No persona loaded." return end personae_result(chosen) end |
#pathname_to_persona_name(pathname) ⇒ String (private)
Converts a persona pathname to its prompt name.
497 498 499 |
# File 'lib/ollama_chat/personae_management.rb', line 497 def pathname_to_persona_name(pathname) pathname.basename.sub_ext('').to_s end |
#persona_backup_pathname(persona) ⇒ Pathname (private)
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.
204 205 206 207 |
# File 'lib/ollama_chat/personae_management.rb', line 204 def persona_backup_pathname(persona) = Time.now.strftime('%Y%m%d%H%M%S') personae_backup_directory + (persona + ?- + + '.md.bak') end |
#persona_description(persona, substitute_variables: false) ⇒ String? (private)
Generates a formatted description of a persona, including its path and profile.
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'lib/ollama_chat/personae_management.rb', line 297 def persona_description(persona, substitute_variables: false) persona_path, persona_profile = load_persona_file(persona) if substitute_variables persona_profile = self.substitute_variables(persona_profile) end persona_profile or return <<~EOT # Persona #{persona} File #{persona_path.to_path} --- #{persona_profile} --- EOT end |
#persona_name_to_pathname(persona_name) ⇒ Pathname (private)
Converts a persona prompt name to its full filesystem pathname.
489 490 491 |
# File 'lib/ollama_chat/personae_management.rb', line 489 def persona_name_to_pathname(persona_name) personae_directory.join(persona_name).sub_ext('.md') end |
#persona_name_with_favourite(name, favourited) ⇒ SearchUI::Wrapper (private)
Helper to wrap a persona name with its favourite status for the UI.
154 155 156 157 |
# File 'lib/ollama_chat/personae_management.rb', line 154 def persona_name_with_favourite(name, favourited) display = prefix_favourite(name, favourited) SearchUI::Wrapper.new(name, display:) end |
#personae_backup_directory ⇒ Pathname (private)
Returns the directory path for persona backup files.
55 56 57 |
# File 'lib/ollama_chat/personae_management.rb', line 55 def personae_backup_directory personae_directory + 'backups' end |
#personae_directory ⇒ Pathname (private)
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.
47 48 49 |
# File 'lib/ollama_chat/personae_management.rb', line 47 def personae_directory OC::XDG_CONFIG_HOME + 'personae' end |
#personae_result(personae) ⇒ String? (private)
Compiles the descriptions for one or more personae into a single string.
Loads the profile for each persona and concatenates their descriptions.
427 428 429 430 431 432 433 434 435 436 437 438 |
# File 'lib/ollama_chat/personae_management.rb', line 427 def personae_result(personae) personae = Array(personae) result = +'' personae.each do |persona| description = persona_description(persona, substitute_variables: true) or next result << description << "\n" end result.full? end |
#play_persona(persona) ⇒ String (private)
Generates the roleplay prompt string for a persona.
Creates a formatted prompt string that includes the persona name and profile.
472 473 474 475 476 477 478 479 480 481 482 483 |
# File 'lib/ollama_chat/personae_management.rb', line 472 def play_persona(persona) pathname, profile = load_persona_file(persona) profile = substitute_variables(profile) profile_intro = <<~EOT Roleplay as persona %{persona} (no nead to read the file) loaded from %{pathname} %{profile} EOT profile_intro % { persona:, pathname:, profile: } end |
#select_persona_path ⇒ String? (private)
Prompts the user to select a persona, copies its filesystem path to the clipboard, and sets it as the prefill prompt for the next interaction.
267 268 269 270 271 272 273 |
# File 'lib/ollama_chat/personae_management.rb', line 267 def select_persona_path persona = choose_persona or return path = persona_name_to_pathname(persona).to_s perform_copy_to_clipboard(text: path) @prefill_prompt = path path end |
#set_default_persona ⇒ String? (private)
Interactively selects a persona and sets it as the default for the session.
This method prompts the user to choose a persona from the available list
using choose_persona. If a valid persona is selected, it updates the
session and the internal state via set_default_persona_name.
93 94 95 96 97 |
# File 'lib/ollama_chat/personae_management.rb', line 93 def set_default_persona if persona = choose_persona(none: true) set_default_persona_name(persona) end end |
#set_default_persona_name(persona_name) ⇒ String? (private)
Sets the default persona name and updates the session.
73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/ollama_chat/personae_management.rb', line 73 def set_default_persona_name(persona_name) if persona_name.present? && persona_name != :none @default_persona_name = Pathname.new(persona_name).basename.sub_ext('').to_path @session.update(default_persona_name: default_persona_name) else @session.update(default_persona_name: nil) @default_persona_name = nil end .set_system_prompt(session&.current_system_prompt.full?) default_persona_name end |
#setup_persona_from_session ⇒ Pathname, ... (private)
Initializes the default persona for the current chat session.
This method ensures that a default persona is configured at startup:
- If a default persona is already set, it returns immediately.
- Otherwise, it attempts to retrieve the initial persona prompt name from the session and verifies if the corresponding Markdown file exists.
- If a valid file is found, it sets it as the default persona.
- The
ensureblock guarantees that the session always has a default persona assigned, falling back to:noneif no valid persona is found.
126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/ollama_chat/personae_management.rb', line 126 def setup_persona_from_session default_persona and return if persona = initial_persona_name and persona_pathname = personae_directory + (persona + '.md') and persona_pathname.exist? then set_default_persona_name(persona_pathname) end ensure default_persona or set_default_persona_name(:none) end |
#setup_personae_directory ⇒ Object (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.
63 64 65 |
# File 'lib/ollama_chat/personae_management.rb', line 63 def setup_personae_directory FileUtils.mkdir_p personae_backup_directory end |
#substitute_variables(profile) ⇒ String (private)
The substitute_variables method handles the substitution of variables in profiles. It replaces placeholders with actual values.
459 460 461 462 463 |
# File 'lib/ollama_chat/personae_management.rb', line 459 def substitute_variables(profile) profile = profile.gsub(/%(?=[^{])/, '%%') profile = profile % { user: } profile end |