module DrawCore
Constants
- AGENT_MODEL
- MAX_ART_LINES
- MAX_ART_WIDTH
- STYLE_TITLE
Public Instance Methods
Source
# File lib/draw_core.rb, line 69 def build_image_prompt(image_path:, style: nil, nick: nil, size: nil) preset = preset_size(size) parts = [ "@#{image_path}", "", "View the attached image and render it as ASCII art for IRC chat.", "", "Requirements:", *irc_ascii_requirements(max_width: preset.art_width, max_lines: preset.art_lines), "- #{style_line(style)}", "- Requested by IRC nick: #{nick}", "", "Output ONLY the coloured ASCII art lines. No markdown fences, no explanation, no file paths." ] AiGuard.guard_prompt(parts.join("\n")) end
Source
# File lib/draw_core.rb, line 86 def build_prompt_prompt(prompt:, style: nil, nick: nil, size: nil) preset = preset_size(size) parts = [ "Create ASCII art for IRC chat from this description:", prompt.to_s.strip, "", "Requirements:", *irc_ascii_requirements(max_width: preset.art_width, max_lines: preset.art_lines), "- #{style_line(style)}", "- Requested by IRC nick: #{nick}", "", "Output ONLY the coloured ASCII art lines. No markdown fences, no explanation." ] AiGuard.guard_prompt(parts.join("\n")) end
Source
# File lib/draw_core.rb, line 111 def extract_art_lines(text, max_width: MAX_ART_WIDTH, max_lines: MAX_ART_LINES) body = text.to_s.gsub(/\A```[^\n]*\n/m, "").gsub(/```\z/, "") body.lines.map(&:rstrip).reject(&:empty?).map { |line| normalize_art_line(line) }.select do |line| plain = strip_irc_codes(line) !plain.empty? && plain.length <= max_width + 20 end.first(max_lines) end
Source
# File lib/draw_core.rb, line 119 def format_header(entry) title = "#{entry['filename']} ยท #{entry['nick']}" IrcFormat.report_header(tag: "DRAW", title: title, style: STYLE_TITLE) end
Source
# File lib/draw_core.rb, line 124 def format_prompt_header(prompt) title = prompt.to_s.strip title = "#{title[0, 40]}..." if title.length > 43 IrcFormat.report_header(tag: "DRAW", title: title, style: STYLE_TITLE) end
Source
# File lib/draw_core.rb, line 138 def format_prompt_reply(prompt:, art_lines:, model:, duration_s:, size: nil) preset = preset_size(size) lines = AsciiDisplaySize.clear_prefix(preset) + [format_prompt_header(prompt)] lines.concat(art_lines) lines << IrcFormat.report_footer("prompt", "cursor", model, "#{duration_s}s") lines.join("\n") end
Source
# File lib/draw_core.rb, line 130 def format_reply(entry:, art_lines:, model:, duration_s:, size: nil) preset = preset_size(size) lines = AsciiDisplaySize.clear_prefix(preset) + [format_header(entry)] lines.concat(art_lines) lines << IrcFormat.report_footer(entry["filename"], "cursor", model, "#{duration_s}s") lines.join("\n") end
Source
# File lib/draw_core.rb, line 59 def irc_ascii_requirements(max_width: MAX_ART_WIDTH, max_lines: MAX_ART_LINES) [ "- Use mIRC colour codes: byte 0x03 (ASCII ctrl-C), format \\003fg,bg where fg and bg are 0-15 (two digits each).", "- After each coloured text run, reset with \\003 alone.", "- Use only the 16 standard mIRC colours (0=white through 15=light grey).", "- Max width #{max_width} characters per line; max #{max_lines} lines of art.", "- Use ASCII characters only (+ - | / \\ # . : o @ * = [ ] etc.) โ no Unicode box art." ] end
Source
# File lib/draw_core.rb, line 44 def locate_entry(entry:, storage_root: Dcc::STORAGE_ROOT) path = Dcc.absolute_path(entry, storage_root: storage_root) return { error: "That image file is no longer available." } unless File.file?(path) { entry: entry, path: path } end
Source
# File lib/draw_core.rb, line 34 def locate_image(network:, nick:, storage_root: Dcc::STORAGE_ROOT, store: RabbotDb) entry = Dcc.last_image_for(network: network, nick: nick, store: store) return { error: "No image on file โ DCC-send a picture to the bot first." } unless entry path = Dcc.absolute_path(entry, storage_root: storage_root) return { error: "That image file is no longer available." } unless File.file?(path) { entry: entry, path: path } end
Source
# File lib/draw_core.rb, line 107 def normalize_art_line(line) TextAlign.dotize(IrcFormat.unescape_literal_color_codes(line)) end
Source
# File lib/draw_core.rb, line 25 def preset_size(size) case size when AsciiDisplaySize::Preset size else AsciiDisplaySize.resolve(size) || AsciiDisplaySize::DEFAULT end end
Source
# File lib/draw_core.rb, line 146 def run_agent_draw(prompt:, runner: nil, model: AGENT_MODEL, size: nil) preset = preset_size(size) result = AiAgent.run_agent(prompt, model: model, runner: runner || AiAgent.method(:default_runner)) unless result.success? message = result.text.to_s.strip return { success: false, reply: message.empty? ? "Draw failed." : message } end filtered = AiGuard.filter_output(result.text) if filtered == AiGuard.rejection_message return { success: false, reply: filtered } end art_lines = extract_art_lines(filtered, max_width: preset.art_width, max_lines: preset.art_lines) return { success: false, reply: "Could not parse ASCII art from the AI response." } if art_lines.empty? { success: true, art_lines: art_lines, model: result.model || model, duration_s: result.duration_s } end
Source
# File lib/draw_core.rb, line 165 def run_image_draw(network:, nick:, style: nil, entry: nil, runner: nil, storage_root: Dcc::STORAGE_ROOT, store: RabbotDb, model: AGENT_MODEL, size: nil) preset = preset_size(size) if style && AiGuard.system_request?(style) return { success: false, reply: AiGuard.rejection_message } end located = if entry locate_entry(entry: entry, storage_root: storage_root) else locate_image(network: network, nick: nick, storage_root: storage_root, store: store) end return { success: false, reply: located[:error] } if located[:error] image_entry = located[:entry] path = located[:path] prompt = build_image_prompt(image_path: path, style: style, nick: nick, size: preset) agent_result = run_agent_draw(prompt: prompt, runner: runner, model: model, size: preset) return agent_result unless agent_result[:success] { success: true, reply: format_reply( entry: image_entry, art_lines: agent_result[:art_lines], model: agent_result[:model], duration_s: agent_result[:duration_s], size: preset ) } end
Source
# File lib/draw_core.rb, line 198 def run_prompt_draw(prompt:, style: nil, nick: nil, runner: nil, model: AGENT_MODEL, size: nil) preset = preset_size(size) text = prompt.to_s.strip return { success: false, reply: "Prompt required." } if text.empty? if AiGuard.system_request?(text) || (style && AiGuard.system_request?(style)) return { success: false, reply: AiGuard.rejection_message } end agent_prompt = build_prompt_prompt(prompt: text, style: style, nick: nick, size: preset) agent_result = run_agent_draw(prompt: agent_prompt, runner: runner, model: model, size: preset) return agent_result unless agent_result[:success] { success: true, reply: format_prompt_reply( prompt: text, art_lines: agent_result[:art_lines], model: agent_result[:model], duration_s: agent_result[:duration_s], size: preset ) } end
Source
# File lib/draw_core.rb, line 102 def strip_irc_codes(line) normalized = IrcFormat.unescape_literal_color_codes(line) normalized.gsub(/\x03\d{1,2}(?:,\d{1,2})?/, "").delete("\x03") end
Source
# File lib/draw_core.rb, line 51 def style_line(style) if style && !style.to_s.strip.empty? "Style / artistic direction: #{style.to_s.strip}" else "Style: faithful ASCII reconstruction with good contrast." end end