Module: CompletionKit::ApplicationHelper
- Defined in:
- app/helpers/completion_kit/application_helper.rb
Constant Summary collapse
- OPENAI_MODEL_FAMILY_ORDER =
["GPT-5", "GPT-4", "o-series", "GPT-3.5", "GPT-OSS", "Other"].freeze
- CHECK_KIND_LABELS =
{ "contains" => "Contains a phrase", "not_contains" => "Does not contain a phrase", "equals" => "Equals exactly", "regex" => "Matches a pattern", "valid_json" => "Is valid JSON", "json_path_equals" => "A JSON field equals a value", "length_bounds" => "Length is within a range" }.freeze
- CHECK_COMPARE_TO_LABELS =
{ "constant" => "A value you type", "expected" => "Each row's expected value" }.freeze
- CHECK_TARGET_LABELS =
{ "response_text" => "The response text", "input_data" => "The input row", "json_path" => "A value from the response JSON" }.freeze
- CHECK_FIELD_LABELS =
{ "value" => "Text to look for", "pattern" => "Pattern", "json_path" => "JSON path", "expected" => "Expected value", "expected_path" => "Field in the answer key", "compare_to" => "Compare against", "target_path" => "Path into the JSON", "min" => "Shortest allowed", "max" => "Longest allowed", "case_sensitive" => "Case sensitive", "multiline" => "Multiline", "trim" => "Trim whitespace" }.freeze
Instance Method Summary collapse
- #ck_badge_classes(kind) ⇒ Object
- #ck_button_classes(tone = :dark, variant: :solid) ⇒ Object
- #ck_check_badge(passed) ⇒ Object
- #ck_check_field_label(key) ⇒ Object
- #ck_check_field_value(key, value) ⇒ Object
- #ck_check_kind_label(kind) ⇒ Object
- #ck_check_target_label(target) ⇒ Object
- #ck_dataset_path(dataset) ⇒ Object
- #ck_delete_trigger(form_id:, label:, confirm: nil) ⇒ Object
-
#ck_engine_path_options ⇒ Object
Dynamic route segments owned by the host's mount scope (e.g. an
org_slugfromscope "/orgs/:org_slug") live inurl_options[:_recall]. - #ck_field_aria(form, field) ⇒ Object
- #ck_field_error(form, field) ⇒ Object
- #ck_field_error_id(form, field) ⇒ Object
- #ck_format_maybe_json(text) ⇒ Object
- #ck_grouped_models(models, selected = nil) ⇒ Object
- #ck_highlight_json(text) ⇒ Object
- #ck_mark_json_keys(tokens) ⇒ Object
- #ck_markdown(text) ⇒ Object
- #ck_masked_token(token) ⇒ Object
-
#ck_model_optgroup_label(model) ⇒ Object
Optgroup label for the model select — mirrors the provider models table: OpenRouter splits by upstream vendor, OpenAI splits by family, everyone else is a single group.
- #ck_model_optgroup_sort_key(label) ⇒ Object
- #ck_model_option_label(model) ⇒ Object
- #ck_model_options_html(scope) ⇒ Object
-
#ck_model_table_sections(models) ⇒ Object
Groups a provider's models for the models-card table, mirroring how the dropdown sub-groups: OpenRouter clusters by upstream vendor (the part before "/"); OpenAI clusters by family; everyone else stays flat.
- #ck_openai_model_family(model_id) ⇒ Object
- #ck_pass_rate_kind(rate) ⇒ Object
- #ck_prompt_path(prompt) ⇒ Object
- #ck_provider_label(provider) ⇒ Object
- #ck_result_change_badge(change) ⇒ Object
- #ck_run_dot(run) ⇒ Object
- #ck_run_path(run) ⇒ Object
- #ck_runs_display_footer(runs) ⇒ Object
- #ck_score_kind(score) ⇒ Object
- #ck_strip_markdown(text) ⇒ Object
- #ck_tokenize_json(text) ⇒ Object
- #ck_unwrap_json_fence(text) ⇒ Object
- #ck_word_diff_new(old_text, new_text) ⇒ Object
- #ck_word_diff_old(old_text, new_text) ⇒ Object
- #tag_filter_url(base_path, selected, toggling) ⇒ Object
- #tag_pill_class(tag, outline: false) ⇒ Object
Instance Method Details
#ck_badge_classes(kind) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'app/helpers/completion_kit/application_helper.rb', line 65 def ck_badge_classes(kind) case kind.to_s when "high" "ck-badge ck-badge--high" when "medium" "ck-badge ck-badge--medium" when "low" "ck-badge ck-badge--low" when "pending" "ck-badge ck-badge--pending" when "running" "ck-badge ck-badge--running" when "completed" "ck-badge ck-badge--high" when "failed" "ck-badge ck-badge--low" else "ck-badge ck-badge--pending" end end |
#ck_button_classes(tone = :dark, variant: :solid) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'app/helpers/completion_kit/application_helper.rb', line 28 def (tone = :dark, variant: :solid) base = "ck-button" styles = case [tone, variant] when [:dark, :solid] "ck-button--primary" when [:light, :outline] "ck-button--secondary" when [:green, :solid] "ck-button--success" when [:red, :outline] "ck-button--danger" when [:amber, :outline] "ck-button--warning" when [:blue, :outline] "ck-button--info" else "ck-button--primary" end "#{base} #{styles}" end |
#ck_check_badge(passed) ⇒ Object
195 196 197 198 199 200 201 202 203 |
# File 'app/helpers/completion_kit/application_helper.rb', line 195 def ck_check_badge(passed) if passed == true content_tag(:span, "Pass", class: ck_badge_classes(:high)) elsif passed == false content_tag(:span, "Fail", class: ck_badge_classes(:low)) else content_tag(:span, "Pending", class: ck_badge_classes(:pending)) end end |
#ck_check_field_label(key) ⇒ Object
249 250 251 |
# File 'app/helpers/completion_kit/application_helper.rb', line 249 def ck_check_field_label(key) CHECK_FIELD_LABELS.fetch(key.to_s) { key.to_s.humanize } end |
#ck_check_field_value(key, value) ⇒ Object
253 254 255 256 257 |
# File 'app/helpers/completion_kit/application_helper.rb', line 253 def ck_check_field_value(key, value) return CHECK_COMPARE_TO_LABELS.fetch(value.to_s, value) if key.to_s == "compare_to" value end |
#ck_check_kind_label(kind) ⇒ Object
241 242 243 |
# File 'app/helpers/completion_kit/application_helper.rb', line 241 def ck_check_kind_label(kind) CHECK_KIND_LABELS.fetch(kind.to_s) { kind.to_s.humanize } end |
#ck_check_target_label(target) ⇒ Object
245 246 247 |
# File 'app/helpers/completion_kit/application_helper.rb', line 245 def ck_check_target_label(target) CHECK_TARGET_LABELS.fetch(target.to_s) { target.to_s.humanize } end |
#ck_dataset_path(dataset) ⇒ Object
297 298 299 |
# File 'app/helpers/completion_kit/application_helper.rb', line 297 def ck_dataset_path(dataset) CompletionKit::Engine.routes.url_helpers.dataset_path(dataset, **) end |
#ck_delete_trigger(form_id:, label:, confirm: nil) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'app/helpers/completion_kit/application_helper.rb', line 51 def ck_delete_trigger(form_id:, label:, confirm: nil) content_tag( :button, type: "submit", form: form_id, class: "ck-icon-btn", title: label, "aria-label": label, data: {turbo_confirm: confirm, ck_confirm_label: "Delete", ck_confirm_tone: "danger"} ) do heroicon_tag "trash", variant: :outline, size: 16, "aria-hidden": "true" end end |
#ck_engine_path_options ⇒ Object
Dynamic route segments owned by the host's mount scope (e.g. an
org_slug from scope "/orgs/:org_slug") live in url_options[:_recall].
The engine's url_helpers won't read them out of the nested recall hash to
satisfy a required segment — they have to arrive as explicit kwargs.
305 306 307 |
# File 'app/helpers/completion_kit/application_helper.rb', line 305 def ([:_recall] || {}).except(:controller, :action, :id, :format) end |
#ck_field_aria(form, field) ⇒ Object
399 400 401 402 |
# File 'app/helpers/completion_kit/application_helper.rb', line 399 def ck_field_aria(form, field) return {} unless form.object.errors[field].any? { "aria-invalid" => "true", "aria-describedby" => ck_field_error_id(form, field) } end |
#ck_field_error(form, field) ⇒ Object
404 405 406 407 |
# File 'app/helpers/completion_kit/application_helper.rb', line 404 def ck_field_error(form, field) return nil unless form.object.errors[field].any? content_tag(:p, form.object.errors[field].first, class: "ck-field-error", id: ck_field_error_id(form, field)) end |
#ck_field_error_id(form, field) ⇒ Object
409 410 411 |
# File 'app/helpers/completion_kit/application_helper.rb', line 409 def ck_field_error_id(form, field) "#{form.object.model_name.param_key}_#{field}_error" end |
#ck_format_maybe_json(text) ⇒ Object
309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'app/helpers/completion_kit/application_helper.rb', line 309 def ck_format_maybe_json(text) s = text.to_s return s if s.strip.empty? payload = ck_unwrap_json_fence(s.strip) first = payload[0] return s unless first == "{" || first == "[" begin ck_highlight_json(JSON.pretty_generate(JSON.parse(payload))) rescue JSON::ParserError s end end |
#ck_grouped_models(models, selected = nil) ⇒ Object
146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'app/helpers/completion_kit/application_helper.rb', line 146 def ck_grouped_models(models, selected = nil) if selected.present? && models.none? { |m| m[:id] == selected } retired = CompletionKit::Model.find_by(model_id: selected) if retired models = models + [{ id: retired.model_id, name: "#{retired.display_name || retired.model_id} (retired)", provider: retired.provider }] end end groups = models.group_by { |m| ck_model_optgroup_label(m) } ordered_keys = groups.keys.sort_by { |label| ck_model_optgroup_sort_key(label) } grouped = ordered_keys.map { |label| [label, groups[label].map { |m| [ck_model_option_label(m), m[:id]] }] } (grouped, selected) end |
#ck_highlight_json(text) ⇒ Object
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'app/helpers/completion_kit/application_helper.rb', line 327 def ck_highlight_json(text) tokens = ck_tokenize_json(text) is_key = ck_mark_json_keys(tokens) parts = tokens.each_with_index.map do |(type, value), idx| escaped = ERB::Util.html_escape(value) case type when :punct then %(<span class="ck-json-punct">#{escaped}</span>) when :string %(<span class="#{is_key[idx] ? "ck-json-key" : "ck-json-string"}">#{escaped}</span>) when :number then %(<span class="ck-json-number">#{escaped}</span>) when :keyword then %(<span class="ck-json-keyword">#{escaped}</span>) else escaped end end parts.join.html_safe end |
#ck_mark_json_keys(tokens) ⇒ Object
383 384 385 386 387 388 389 390 |
# File 'app/helpers/completion_kit/application_helper.rb', line 383 def ck_mark_json_keys(tokens) tokens.each_with_index.map do |(type, _), idx| next false unless type == :string j = idx + 1 j += 1 while j < tokens.length && tokens[j][0] == :ws j < tokens.length && tokens[j] == [:punct, ":"] end end |
#ck_markdown(text) ⇒ Object
3 4 5 6 7 8 9 10 |
# File 'app/helpers/completion_kit/application_helper.rb', line 3 def ck_markdown(text) return "".html_safe if text.blank? inline = text.to_s .gsub(/\*\*(.+?)\*\*/, '<strong>\1</strong>') .gsub(/`([^`]+)`/, '<code>\1</code>') simple_format(inline) end |
#ck_masked_token(token) ⇒ Object
100 101 102 103 104 |
# File 'app/helpers/completion_kit/application_helper.rb', line 100 def ck_masked_token(token) return "YOUR_TOKEN" if token.blank? return "••••••••" if token.length < 12 "#{token[0..3]}#{'•' * [token.length - 8, 4].max}#{token[-4..]}" end |
#ck_model_optgroup_label(model) ⇒ Object
Optgroup label for the model select — mirrors the provider models table: OpenRouter splits by upstream vendor, OpenAI splits by family, everyone else is a single group.
163 164 165 166 167 168 169 |
# File 'app/helpers/completion_kit/application_helper.rb', line 163 def ck_model_optgroup_label(model) case model[:provider] when "openrouter" then "OpenRouter — #{model[:id].to_s.split("/", 2).first.delete_prefix("~")}" when "openai" then "OpenAI — #{ck_openai_model_family(model[:id])}" else ck_provider_label(model[:provider]) end end |
#ck_model_optgroup_sort_key(label) ⇒ Object
171 172 173 174 175 176 177 178 179 |
# File 'app/helpers/completion_kit/application_helper.rb', line 171 def ck_model_optgroup_sort_key(label) if label.start_with?("OpenAI — ") [0, OPENAI_MODEL_FAMILY_ORDER.index(label.delete_prefix("OpenAI — ")), label] elsif label.start_with?("OpenRouter") [2, 0, label] else [1, 0, label] end end |
#ck_model_option_label(model) ⇒ Object
141 142 143 144 |
# File 'app/helpers/completion_kit/application_helper.rb', line 141 def ck_model_option_label(model) return "#{model[:name]} (?)" if model.key?(:judging_confirmed) && !model[:judging_confirmed] model[:name] end |
#ck_model_options_html(scope) ⇒ Object
181 182 183 184 185 |
# File 'app/helpers/completion_kit/application_helper.rb', line 181 def (scope) models = CompletionKit::ApiConfig.available_models(scope: scope) return "" if models.empty? ck_grouped_models(models) end |
#ck_model_table_sections(models) ⇒ Object
Groups a provider's models for the models-card table, mirroring how the dropdown sub-groups: OpenRouter clusters by upstream vendor (the part before "/"); OpenAI clusters by family; everyone else stays flat. Returns [[section_label_or_nil, [models]], ...]. A single section collapses to a nil label so we don't render a redundant header.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'app/helpers/completion_kit/application_helper.rb', line 123 def ck_model_table_sections(models) models = models.to_a sections = case models.first&.provider when "openrouter" models.group_by { |m| m.model_id.to_s.split("/", 2).first.delete_prefix("~") } .sort_by { |label, _| label } when "openai" grouped = models.group_by { |m| ck_openai_model_family(m.model_id) } ordered = OPENAI_MODEL_FAMILY_ORDER.filter_map { |label| [label, grouped[label]] if grouped[label] } extras = (grouped.keys - OPENAI_MODEL_FAMILY_ORDER).sort.map { |label| [label, grouped[label]] } ordered + extras else [[nil, models]] end sections.size <= 1 ? [[nil, models]] : sections end |
#ck_openai_model_family(model_id) ⇒ Object
108 109 110 111 112 113 114 115 116 |
# File 'app/helpers/completion_kit/application_helper.rb', line 108 def ck_openai_model_family(model_id) id = model_id.to_s return "GPT-5" if id.match?(/\Agpt-5/i) return "GPT-4" if id.match?(/\Agpt-4/i) return "GPT-3.5" if id.match?(/\Agpt-3/i) return "GPT-OSS" if id.match?(/\Agpt-oss/i) return "o-series" if id.match?(/\Ao\d/i) "Other" end |
#ck_pass_rate_kind(rate) ⇒ Object
270 271 272 273 274 275 |
# File 'app/helpers/completion_kit/application_helper.rb', line 270 def ck_pass_rate_kind(rate) return :high if rate >= 0.9 return :medium if rate >= 0.7 :low end |
#ck_prompt_path(prompt) ⇒ Object
293 294 295 |
# File 'app/helpers/completion_kit/application_helper.rb', line 293 def ck_prompt_path(prompt) CompletionKit::Engine.routes.url_helpers.prompt_path(prompt, **) end |
#ck_provider_label(provider) ⇒ Object
96 97 98 |
# File 'app/helpers/completion_kit/application_helper.rb', line 96 def ck_provider_label(provider) CompletionKit::ProviderCredential::PROVIDER_LABELS[provider.to_s] || provider.to_s.titleize end |
#ck_result_change_badge(change) ⇒ Object
259 260 261 262 263 264 265 266 267 268 |
# File 'app/helpers/completion_kit/application_helper.rb', line 259 def ck_result_change_badge(change) case change when "broke" content_tag(:span, "Broke", class: "ck-delta ck-delta--negative") when "fixed" content_tag(:span, "Fixed", class: "ck-delta ck-delta--positive") when "same" content_tag(:span, "Same", class: "ck-delta ck-delta--zero") end end |
#ck_run_dot(run) ⇒ Object
86 87 88 89 90 91 92 93 |
# File 'app/helpers/completion_kit/application_helper.rb', line 86 def ck_run_dot(run) case run.status when "running" then "ck-dot ck-dot--running" when "failed" then "ck-dot ck-dot--failed" when "completed" then "ck-dot ck-dot--completed" else "ck-dot ck-dot--pending" end end |
#ck_run_path(run) ⇒ Object
289 290 291 |
# File 'app/helpers/completion_kit/application_helper.rb', line 289 def ck_run_path(run) CompletionKit::Engine.routes.url_helpers.run_path(run, **) end |
#ck_runs_display_footer(runs) ⇒ Object
22 23 24 25 26 |
# File 'app/helpers/completion_kit/application_helper.rb', line 22 def (runs) partial = CompletionKit.config. return unless partial render partial, runs: runs end |
#ck_score_kind(score) ⇒ Object
187 188 189 190 191 192 193 |
# File 'app/helpers/completion_kit/application_helper.rb', line 187 def ck_score_kind(score) return :pending if score.nil? return :high if score >= CompletionKit.config.high_quality_threshold return :medium if score >= CompletionKit.config.medium_quality_threshold :low end |
#ck_strip_markdown(text) ⇒ Object
12 13 14 15 16 17 18 19 20 |
# File 'app/helpers/completion_kit/application_helper.rb', line 12 def ck_strip_markdown(text) text.to_s .gsub(/\*\*(.+?)\*\*/, '\1') .gsub(/`([^`]+)`/, '\1') .gsub(/[#>]+/, " ") .gsub(/(?:^|\s)[-*•]\s+/, " ") .gsub(/\s+/, " ") .strip end |
#ck_tokenize_json(text) ⇒ Object
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 369 370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'app/helpers/completion_kit/application_helper.rb', line 344 def ck_tokenize_json(text) tokens = [] i = 0 len = text.length while i < len ch = text[i] if ch == " " || ch == "\n" || ch == "\t" tokens << [:ws, ch] i += 1 elsif "{}[]:,".include?(ch) tokens << [:punct, ch] i += 1 elsif ch == '"' j = i + 1 while j < len && text[j] != '"' j += text[j] == "\\" ? 2 : 1 end j = len - 1 if j >= len tokens << [:string, text[i..j]] i = j + 1 elsif ch == "-" || (ch >= "0" && ch <= "9") j = i + 1 j += 1 while j < len && "0123456789.eE+-".include?(text[j]) tokens << [:number, text[i...j]] i = j elsif text[i, 4] == "true" || text[i, 4] == "null" tokens << [:keyword, text[i, 4]] i += 4 elsif text[i, 5] == "false" tokens << [:keyword, "false"] i += 5 else tokens << [:other, ch] i += 1 end end tokens end |
#ck_unwrap_json_fence(text) ⇒ Object
322 323 324 325 |
# File 'app/helpers/completion_kit/application_helper.rb', line 322 def ck_unwrap_json_fence(text) m = text.match(/\A```(?:json|JSON)?\s*\n(.*?)\n?```\s*\z/m) m ? m[1].strip : text end |
#ck_word_diff_new(old_text, new_text) ⇒ Object
281 282 283 |
# File 'app/helpers/completion_kit/application_helper.rb', line 281 def ck_word_diff_new(old_text, new_text) diff_tokens(old_text, new_text, :new) end |
#ck_word_diff_old(old_text, new_text) ⇒ Object
277 278 279 |
# File 'app/helpers/completion_kit/application_helper.rb', line 277 def ck_word_diff_old(old_text, new_text) diff_tokens(old_text, new_text, :old) end |
#tag_filter_url(base_path, selected, toggling) ⇒ Object
392 393 394 395 396 397 |
# File 'app/helpers/completion_kit/application_helper.rb', line 392 def tag_filter_url(base_path, selected, toggling) remaining = selected.reject { |t| t.id == toggling.id } next_set = selected.include?(toggling) ? remaining : remaining + [toggling] return base_path if next_set.empty? "#{base_path}?#{{ tag: next_set.map(&:name) }.to_query}" end |
#tag_pill_class(tag, outline: false) ⇒ Object
285 286 287 |
# File 'app/helpers/completion_kit/application_helper.rb', line 285 def tag_pill_class(tag, outline: false) ["tag", "tag-#{tag.color}", ("tag-outline" if outline)].compact.join(" ") end |