completion-kit 0.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +192 -0
- data/Rakefile +12 -0
- data/app/assets/config/completion_kit_manifest.js +1 -0
- data/app/assets/config/manifest.js +3 -0
- data/app/assets/images/completion_kit/logo.svg +6 -0
- data/app/assets/javascripts/completion_kit/evaluation_steps_controller.js +25 -0
- data/app/assets/stylesheets/completion_kit/application.css +2214 -0
- data/app/controllers/completion_kit/api/v1/base_controller.rb +29 -0
- data/app/controllers/completion_kit/api/v1/criteria_controller.rb +62 -0
- data/app/controllers/completion_kit/api/v1/datasets_controller.rb +51 -0
- data/app/controllers/completion_kit/api/v1/metrics_controller.rb +51 -0
- data/app/controllers/completion_kit/api/v1/prompts_controller.rb +64 -0
- data/app/controllers/completion_kit/api/v1/provider_credentials_controller.rb +51 -0
- data/app/controllers/completion_kit/api/v1/responses_controller.rb +32 -0
- data/app/controllers/completion_kit/api/v1/runs_controller.rb +71 -0
- data/app/controllers/completion_kit/api_reference_controller.rb +9 -0
- data/app/controllers/completion_kit/application_controller.rb +31 -0
- data/app/controllers/completion_kit/criteria_controller.rb +67 -0
- data/app/controllers/completion_kit/datasets_controller.rb +53 -0
- data/app/controllers/completion_kit/mcp_controller.rb +57 -0
- data/app/controllers/completion_kit/metrics_controller.rb +52 -0
- data/app/controllers/completion_kit/prompts_controller.rb +69 -0
- data/app/controllers/completion_kit/provider_credentials_controller.rb +63 -0
- data/app/controllers/completion_kit/responses_controller.rb +44 -0
- data/app/controllers/completion_kit/runs_controller.rb +131 -0
- data/app/helpers/completion_kit/application_helper.rb +193 -0
- data/app/jobs/completion_kit/application_job.rb +4 -0
- data/app/jobs/completion_kit/generate_job.rb +12 -0
- data/app/jobs/completion_kit/judge_job.rb +12 -0
- data/app/jobs/completion_kit/model_discovery_job.rb +29 -0
- data/app/mailers/completion_kit/application_mailer.rb +6 -0
- data/app/models/completion_kit/application_record.rb +5 -0
- data/app/models/completion_kit/criteria.rb +22 -0
- data/app/models/completion_kit/criteria_membership.rb +20 -0
- data/app/models/completion_kit/dataset.rb +24 -0
- data/app/models/completion_kit/metric.rb +97 -0
- data/app/models/completion_kit/model.rb +13 -0
- data/app/models/completion_kit/prompt.rb +99 -0
- data/app/models/completion_kit/provider_credential.rb +114 -0
- data/app/models/completion_kit/response.rb +30 -0
- data/app/models/completion_kit/review.rb +28 -0
- data/app/models/completion_kit/run.rb +253 -0
- data/app/models/completion_kit/run_metric.rb +6 -0
- data/app/models/completion_kit/suggestion.rb +8 -0
- data/app/services/completion_kit/anthropic_client.rb +86 -0
- data/app/services/completion_kit/api_config.rb +80 -0
- data/app/services/completion_kit/csv_processor.rb +65 -0
- data/app/services/completion_kit/judge_service.rb +87 -0
- data/app/services/completion_kit/llm_client.rb +45 -0
- data/app/services/completion_kit/mcp_dispatcher.rb +53 -0
- data/app/services/completion_kit/mcp_tools/criteria.rb +106 -0
- data/app/services/completion_kit/mcp_tools/datasets.rb +90 -0
- data/app/services/completion_kit/mcp_tools/metrics.rb +98 -0
- data/app/services/completion_kit/mcp_tools/prompts.rb +112 -0
- data/app/services/completion_kit/mcp_tools/provider_credentials.rb +97 -0
- data/app/services/completion_kit/mcp_tools/responses.rb +45 -0
- data/app/services/completion_kit/mcp_tools/runs.rb +130 -0
- data/app/services/completion_kit/model_discovery_service.rb +223 -0
- data/app/services/completion_kit/ollama_client.rb +80 -0
- data/app/services/completion_kit/open_ai_client.rb +71 -0
- data/app/services/completion_kit/open_router_client.rb +69 -0
- data/app/services/completion_kit/prompt_improvement_service.rb +81 -0
- data/app/views/completion_kit/api_reference/_example.html.erb +6 -0
- data/app/views/completion_kit/api_reference/index.html.erb +308 -0
- data/app/views/completion_kit/criteria/_form.html.erb +46 -0
- data/app/views/completion_kit/criteria/edit.html.erb +14 -0
- data/app/views/completion_kit/criteria/index.html.erb +37 -0
- data/app/views/completion_kit/criteria/new.html.erb +13 -0
- data/app/views/completion_kit/criteria/show.html.erb +37 -0
- data/app/views/completion_kit/datasets/_form.html.erb +29 -0
- data/app/views/completion_kit/datasets/edit.html.erb +13 -0
- data/app/views/completion_kit/datasets/index.html.erb +38 -0
- data/app/views/completion_kit/datasets/new.html.erb +12 -0
- data/app/views/completion_kit/datasets/show.html.erb +45 -0
- data/app/views/completion_kit/metrics/_form.html.erb +72 -0
- data/app/views/completion_kit/metrics/edit.html.erb +13 -0
- data/app/views/completion_kit/metrics/index.html.erb +34 -0
- data/app/views/completion_kit/metrics/new.html.erb +12 -0
- data/app/views/completion_kit/metrics/show.html.erb +49 -0
- data/app/views/completion_kit/prompts/_form.html.erb +52 -0
- data/app/views/completion_kit/prompts/edit.html.erb +13 -0
- data/app/views/completion_kit/prompts/index.html.erb +46 -0
- data/app/views/completion_kit/prompts/new.html.erb +12 -0
- data/app/views/completion_kit/prompts/show.html.erb +156 -0
- data/app/views/completion_kit/provider_credentials/_discovery_status.html.erb +30 -0
- data/app/views/completion_kit/provider_credentials/_form.html.erb +71 -0
- data/app/views/completion_kit/provider_credentials/edit.html.erb +12 -0
- data/app/views/completion_kit/provider_credentials/index.html.erb +41 -0
- data/app/views/completion_kit/provider_credentials/new.html.erb +12 -0
- data/app/views/completion_kit/responses/show.html.erb +87 -0
- data/app/views/completion_kit/runs/_actions.html.erb +14 -0
- data/app/views/completion_kit/runs/_form.html.erb +159 -0
- data/app/views/completion_kit/runs/_progress.html.erb +18 -0
- data/app/views/completion_kit/runs/_response_row.html.erb +13 -0
- data/app/views/completion_kit/runs/_sort_toolbar.html.erb +8 -0
- data/app/views/completion_kit/runs/_status_header.html.erb +15 -0
- data/app/views/completion_kit/runs/edit.html.erb +14 -0
- data/app/views/completion_kit/runs/index.html.erb +43 -0
- data/app/views/completion_kit/runs/new.html.erb +12 -0
- data/app/views/completion_kit/runs/show.html.erb +79 -0
- data/app/views/completion_kit/runs/suggestion.html.erb +47 -0
- data/app/views/layouts/completion_kit/application.html.erb +77 -0
- data/config/routes.rb +55 -0
- data/db/migrate/20260311000001_create_completion_kit_tables.rb +87 -0
- data/db/migrate/20260326000001_rename_criteria_to_instruction_on_metrics_and_reviews.rb +6 -0
- data/db/migrate/20260327000001_add_progress_to_runs.rb +6 -0
- data/db/migrate/20260327100001_replace_criteria_with_direct_metrics_on_runs.rb +12 -0
- data/db/migrate/20260328000001_add_error_message_to_runs.rb +5 -0
- data/db/migrate/20260329000001_create_completion_kit_models.rb +20 -0
- data/db/migrate/20260401170001_add_discovery_columns_to_completion_kit_provider_credentials.rb +7 -0
- data/db/migrate/20260403000001_add_temperature_to_completion_kit_runs.rb +5 -0
- data/db/migrate/20260403000002_create_completion_kit_suggestions.rb +13 -0
- data/db/migrate/20260403000003_add_applied_at_to_completion_kit_suggestions.rb +5 -0
- data/lib/completion-kit.rb +1 -0
- data/lib/completion_kit/engine.rb +35 -0
- data/lib/completion_kit/version.rb +3 -0
- data/lib/completion_kit.rb +55 -0
- data/lib/generators/completion_kit/install_generator.rb +21 -0
- data/lib/generators/completion_kit/templates/README +20 -0
- data/lib/generators/completion_kit/templates/initializer.rb +43 -0
- metadata +361 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<%= form_with(model: metric, local: true) do |form| %>
|
|
2
|
+
<% if metric.errors.any? %>
|
|
3
|
+
<div class="ck-flash ck-flash--alert">
|
|
4
|
+
<p class="ck-flash__title"><%= pluralize(metric.errors.count, "problem") %> prevented this metric from being saved.</p>
|
|
5
|
+
<ul class="ck-error-list">
|
|
6
|
+
<% metric.errors.full_messages.each do |message| %>
|
|
7
|
+
<li><%= message %></li>
|
|
8
|
+
<% end %>
|
|
9
|
+
</ul>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<div class="ck-card ck-form-card">
|
|
14
|
+
<div class="ck-field">
|
|
15
|
+
<%= form.label :name, "Metric name", class: "ck-label" %>
|
|
16
|
+
<%= form.text_field :name, class: "ck-input", placeholder: "Helpfulness" %>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="ck-field ck-field--spacious">
|
|
20
|
+
<p class="ck-section-title">Evaluation Instruction</p>
|
|
21
|
+
<p class="ck-hint">What should the evaluator assess? This is the core instruction sent to the judge model.</p>
|
|
22
|
+
<%= form.text_area :instruction, rows: 8, class: "ck-input ck-input--area", placeholder: "Evaluate whether the output..." %>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="ck-field ck-field--spacious" data-controller="evaluation-steps">
|
|
26
|
+
<p class="ck-section-title">Evaluation Steps</p>
|
|
27
|
+
<p class="ck-hint">Steps the evaluator follows during assessment.</p>
|
|
28
|
+
|
|
29
|
+
<div data-evaluation-steps-target="list">
|
|
30
|
+
<% Array(metric.evaluation_steps).each_with_index do |step, index| %>
|
|
31
|
+
<div class="ck-step-row" data-evaluation-steps-target="row">
|
|
32
|
+
<input type="text" name="metric[evaluation_steps][]" value="<%= step %>" class="ck-input" />
|
|
33
|
+
<button type="button" class="ck-icon-btn" data-action="evaluation-steps#remove" aria-label="Remove step">
|
|
34
|
+
<%= heroicon_tag "trash", variant: :outline, size: 16 %>
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
<% end %>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<button type="button" class="ck-add-btn" data-action="evaluation-steps#add">
|
|
41
|
+
<%= heroicon_tag "plus", variant: :outline, size: 14 %>
|
|
42
|
+
Add step
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="ck-field ck-field--spacious">
|
|
47
|
+
<p class="ck-section-title">Rubric</p>
|
|
48
|
+
<p class="ck-hint">What each star rating means for this metric.</p>
|
|
49
|
+
|
|
50
|
+
<div class="ck-rubric-builder">
|
|
51
|
+
<% metric.rubric_bands_for_form.each_with_index do |band, index| %>
|
|
52
|
+
<div class="ck-rubric-row">
|
|
53
|
+
<div class="ck-rubric-row__stars">
|
|
54
|
+
<% 5.times do |i| %>
|
|
55
|
+
<svg viewBox="0 0 24 24" width="14" height="14" stroke-width="1.75" class="ck-star <%= i < band["stars"] ? "ck-star--filled" : "ck-star--empty" %>"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
|
56
|
+
<% end %>
|
|
57
|
+
<input type="hidden" name="metric[rubric_bands][<%= index %>][stars]" value="<%= band["stars"] %>">
|
|
58
|
+
</div>
|
|
59
|
+
<div class="ck-rubric-row__fields">
|
|
60
|
+
<textarea name="metric[rubric_bands][<%= index %>][description]" rows="2" class="ck-input ck-input--area"><%= band["description"] %></textarea>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<% end %>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="ck-actions">
|
|
68
|
+
<%= link_to "Cancel", metrics_path, class: ck_button_classes(:light, variant: :outline) %>
|
|
69
|
+
<%= form.submit(metric.persisted? ? "Save metric" : "Create metric", class: ck_button_classes(:dark)) %>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<ol class="ck-breadcrumb">
|
|
2
|
+
<li><%= link_to "Metrics", metrics_path %></li>
|
|
3
|
+
<li><%= link_to @metric.name, metric_path(@metric) %></li>
|
|
4
|
+
<li>Edit</li>
|
|
5
|
+
</ol>
|
|
6
|
+
|
|
7
|
+
<section class="ck-page-header">
|
|
8
|
+
<div>
|
|
9
|
+
<h1 class="ck-title">Edit metric</h1>
|
|
10
|
+
</div>
|
|
11
|
+
</section>
|
|
12
|
+
|
|
13
|
+
<%= render "form", metric: @metric %>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<section class="ck-page-header">
|
|
2
|
+
<div>
|
|
3
|
+
<h1 class="ck-title">Metrics</h1>
|
|
4
|
+
<p class="ck-lead">Scoring dimensions used by the judge model to evaluate responses. Each metric defines what to assess, the evaluation steps, and a rubric for star ratings. Group metrics into <%= link_to "Criteria", criteria_path, class: "ck-link" %> for reuse across runs.</p>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="ck-actions">
|
|
7
|
+
<%= link_to "New metric", new_metric_path, class: ck_button_classes(:dark) %>
|
|
8
|
+
</div>
|
|
9
|
+
</section>
|
|
10
|
+
|
|
11
|
+
<% if @metrics.any? %>
|
|
12
|
+
<table class="ck-results-table">
|
|
13
|
+
<thead>
|
|
14
|
+
<tr>
|
|
15
|
+
<th>Name</th>
|
|
16
|
+
<th>Instruction</th>
|
|
17
|
+
<th>Criteria</th>
|
|
18
|
+
<th></th>
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
<% @metrics.each do |metric| %>
|
|
23
|
+
<tr onclick="window.location='<%= metric_path(metric) %>'" style="cursor: pointer;">
|
|
24
|
+
<td><strong><%= metric.name %></strong></td>
|
|
25
|
+
<td class="ck-meta-copy"><%= truncate(metric.instruction.to_s, length: 80).presence || "None" %></td>
|
|
26
|
+
<td class="ck-meta-copy"><%= metric.criterias.any? ? metric.criterias.map { |c| link_to(c.name, criterion_path(c), class: "ck-link", onclick: "event.stopPropagation()") }.join(", ").html_safe : "None" %></td>
|
|
27
|
+
<td class="ck-results-table__arrow">→</td>
|
|
28
|
+
</tr>
|
|
29
|
+
<% end %>
|
|
30
|
+
</tbody>
|
|
31
|
+
</table>
|
|
32
|
+
<% else %>
|
|
33
|
+
<div class="ck-empty">No metrics yet.</div>
|
|
34
|
+
<% end %>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<ol class="ck-breadcrumb">
|
|
2
|
+
<li><%= link_to "Metrics", metrics_path %></li>
|
|
3
|
+
<li><%= @metric.name %></li>
|
|
4
|
+
</ol>
|
|
5
|
+
|
|
6
|
+
<section class="ck-page-header">
|
|
7
|
+
<div>
|
|
8
|
+
<h1 class="ck-title"><%= @metric.name %></h1>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="ck-actions">
|
|
11
|
+
<%= link_to "Edit", edit_metric_path(@metric), class: ck_button_classes(:light, variant: :outline) %>
|
|
12
|
+
</div>
|
|
13
|
+
</section>
|
|
14
|
+
|
|
15
|
+
<% if @metric.instruction.present? %>
|
|
16
|
+
<section class="ck-card">
|
|
17
|
+
<p class="ck-kicker">Evaluation Instruction</p>
|
|
18
|
+
<div class="ck-note-box"><%= simple_format(@metric.instruction) %></div>
|
|
19
|
+
</section>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<% if @metric.evaluation_steps.present? && @metric.evaluation_steps.any? %>
|
|
23
|
+
<section class="ck-card">
|
|
24
|
+
<p class="ck-kicker">Evaluation Steps</p>
|
|
25
|
+
<ol class="ck-step-list">
|
|
26
|
+
<% @metric.evaluation_steps.each do |step| %>
|
|
27
|
+
<li><%= step %></li>
|
|
28
|
+
<% end %>
|
|
29
|
+
</ol>
|
|
30
|
+
</section>
|
|
31
|
+
<% end %>
|
|
32
|
+
|
|
33
|
+
<section class="ck-card ck-card--spaced">
|
|
34
|
+
<p class="ck-kicker">Rubric</p>
|
|
35
|
+
<div class="ck-rubric-display">
|
|
36
|
+
<% @metric.rubric_bands_for_form.each do |band| %>
|
|
37
|
+
<div class="ck-rubric-row ck-rubric-row--display">
|
|
38
|
+
<div class="ck-rubric-row__stars">
|
|
39
|
+
<% 5.times do |i| %>
|
|
40
|
+
<svg viewBox="0 0 24 24" width="14" height="14" stroke-width="1.75" class="ck-star <%= i < band["stars"] ? "ck-star--filled" : "ck-star--empty" %>"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
|
41
|
+
<% end %>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="ck-rubric-row__fields">
|
|
44
|
+
<p class="ck-copy"><%= band["description"] %></p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<% end %>
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<%= form_with(model: prompt, local: true) do |form| %>
|
|
2
|
+
<% if prompt.errors.any? %>
|
|
3
|
+
<div class="ck-flash ck-flash--alert">
|
|
4
|
+
<p class="ck-flash__title"><%= pluralize(prompt.errors.count, "problem") %> prevented this prompt from being saved.</p>
|
|
5
|
+
<ul class="ck-error-list">
|
|
6
|
+
<% prompt.errors.full_messages.each do |message| %>
|
|
7
|
+
<li><%= message %></li>
|
|
8
|
+
<% end %>
|
|
9
|
+
</ul>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<div class="ck-card ck-form-card">
|
|
14
|
+
<div class="ck-field">
|
|
15
|
+
<%= form.label :name, "Name", class: "ck-label" %>
|
|
16
|
+
<%= form.text_field :name, class: "ck-input", placeholder: "Support summary", autocomplete: "off", data: { "1p-ignore": "" } %>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="ck-field">
|
|
20
|
+
<%= form.label :description, class: "ck-label" %>
|
|
21
|
+
<%= form.text_area :description, rows: 3, class: "ck-input ck-input--area", placeholder: "Short note about this prompt." %>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="ck-field">
|
|
25
|
+
<%= form.label :template, "Prompt text", class: "ck-label" %>
|
|
26
|
+
<%= form.text_area :template, rows: 12, class: "ck-input ck-input--area ck-input--code", placeholder: "Summarize {{content}} for {{audience}}" %>
|
|
27
|
+
<p class="ck-hint">Use <code>{{variable}}</code>. Match your dataset column names.</p>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="ck-field">
|
|
31
|
+
<%= form.label :llm_model, "Generation model", class: "ck-label" %>
|
|
32
|
+
<% available = CompletionKit::ApiConfig.available_models(scope: :generation) %>
|
|
33
|
+
<% if available.any? %>
|
|
34
|
+
<div class="ck-select-with-action">
|
|
35
|
+
<%= form.select :llm_model, ck_grouped_models(available, prompt.llm_model), {}, { class: "ck-input", id: "prompt_llm_model" } %>
|
|
36
|
+
<button type="button" class="ck-icon-btn" title="Refresh models" onclick="ckRefreshModels()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.681.75.75 0 0 1-1.264-.808 6 6 0 0 1 9.44-.908l.84.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44.908l-.84-.84v1.68a.75.75 0 0 1-1.5 0V9.567a.75.75 0 0 1 .75-.75h3.182a.75.75 0 0 1 0 1.5h-1.37l.84.841a4.5 4.5 0 0 0 7.08-.681.75.75 0 0 1 1.024-.274Z" clip-rule="evenodd"/></svg></button>
|
|
37
|
+
</div>
|
|
38
|
+
<% else %>
|
|
39
|
+
<p class="ck-meta-copy">No models available. <%= link_to "Add a provider", provider_credentials_path, class: "ck-link" %> or click refresh after configuring a provider.</p>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% CompletionKit::ProviderCredential.find_each do |pc| %>
|
|
42
|
+
<%= turbo_stream_from "completion_kit_provider_#{pc.id}" %>
|
|
43
|
+
<% end %>
|
|
44
|
+
<p class="ck-field-hint" id="refresh-status" style="min-height: 1.2em; margin-top: -0.25rem; font-size: 0.75rem;"> </p>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="ck-actions">
|
|
48
|
+
<%= link_to "Cancel", prompts_path, class: ck_button_classes(:light, variant: :outline) %>
|
|
49
|
+
<%= form.submit(prompt.persisted? ? "Save prompt" : "Create prompt", class: ck_button_classes(:dark)) %>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<ol class="ck-breadcrumb">
|
|
2
|
+
<li><%= link_to "Prompts", prompts_path %></li>
|
|
3
|
+
<li><%= link_to @prompt.name, prompt_path(@prompt) %></li>
|
|
4
|
+
<li>Edit</li>
|
|
5
|
+
</ol>
|
|
6
|
+
|
|
7
|
+
<section class="ck-page-header">
|
|
8
|
+
<div>
|
|
9
|
+
<h1 class="ck-title">Edit prompt</h1>
|
|
10
|
+
</div>
|
|
11
|
+
</section>
|
|
12
|
+
|
|
13
|
+
<%= render "form", prompt: @prompt %>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<section class="ck-page-header">
|
|
2
|
+
<div>
|
|
3
|
+
<h1 class="ck-title">Prompts</h1>
|
|
4
|
+
<p class="ck-lead">Prompts sent to LLMs for completion. Each prompt is versioned so you can track changes and compare results over time.</p>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="ck-actions">
|
|
7
|
+
<%= link_to "New prompt", new_prompt_path, class: ck_button_classes(:dark) %>
|
|
8
|
+
</div>
|
|
9
|
+
</section>
|
|
10
|
+
|
|
11
|
+
<% if @prompts.any? %>
|
|
12
|
+
<table class="ck-results-table">
|
|
13
|
+
<thead>
|
|
14
|
+
<tr>
|
|
15
|
+
<th>Name</th>
|
|
16
|
+
<th>Model</th>
|
|
17
|
+
<th>Runs</th>
|
|
18
|
+
<th>Last run</th>
|
|
19
|
+
<th></th>
|
|
20
|
+
</tr>
|
|
21
|
+
</thead>
|
|
22
|
+
<tbody>
|
|
23
|
+
<% @prompts.each do |prompt| %>
|
|
24
|
+
<tr onclick="window.location='<%= prompt_path(prompt) %>'" style="cursor: pointer;">
|
|
25
|
+
<td><strong><%= prompt.name %></strong> <span class="ck-chip ck-chip--soft"><%= prompt.version_label %></span></td>
|
|
26
|
+
<td><span class="ck-chip"><%= prompt.llm_model %></span></td>
|
|
27
|
+
<% family_runs = CompletionKit::Run.where(prompt_id: prompt.family_versions.select(:id)) %>
|
|
28
|
+
<td><%= family_runs.count %></td>
|
|
29
|
+
<td>
|
|
30
|
+
<% last_run = family_runs.order(created_at: :desc).first %>
|
|
31
|
+
<% if last_run %>
|
|
32
|
+
<%= time_ago_in_words(last_run.created_at) %> ago
|
|
33
|
+
<% else %>
|
|
34
|
+
Never
|
|
35
|
+
<% end %>
|
|
36
|
+
</td>
|
|
37
|
+
<td class="ck-results-table__arrow">→</td>
|
|
38
|
+
</tr>
|
|
39
|
+
<% end %>
|
|
40
|
+
</tbody>
|
|
41
|
+
</table>
|
|
42
|
+
<% else %>
|
|
43
|
+
<div class="ck-empty">
|
|
44
|
+
No prompts yet. Create one to get started.
|
|
45
|
+
</div>
|
|
46
|
+
<% end %>
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<ol class="ck-breadcrumb">
|
|
2
|
+
<li><%= link_to "Prompts", prompts_path %></li>
|
|
3
|
+
<li><%= @prompt.name %></li>
|
|
4
|
+
</ol>
|
|
5
|
+
|
|
6
|
+
<section class="ck-page-header">
|
|
7
|
+
<div>
|
|
8
|
+
<div class="ck-inline">
|
|
9
|
+
<h1 class="ck-title"><%= @prompt.name %></h1>
|
|
10
|
+
<span class="ck-chip"><%= @prompt.llm_model %></span>
|
|
11
|
+
</div>
|
|
12
|
+
<% if @prompt.description.present? %>
|
|
13
|
+
<p class="ck-meta-copy"><%= @prompt.description %></p>
|
|
14
|
+
<% end %>
|
|
15
|
+
<div class="ck-endpoint">
|
|
16
|
+
<code class="ck-endpoint__url" id="prompt_endpoint"><%= request.base_url %><%= api_v1_prompt_path(@prompt.slug) %></code>
|
|
17
|
+
<button type="button" class="ck-icon-btn" title="Copy endpoint" onclick="navigator.clipboard.writeText(document.getElementById('prompt_endpoint').textContent)"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" width="14" height="14"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/></svg></button>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="ck-actions">
|
|
22
|
+
<% current = @prompt.family_versions.find_by(current: true) || @prompt %>
|
|
23
|
+
<%= link_to "Edit", edit_prompt_path(current), class: ck_button_classes(:light, variant: :outline) %>
|
|
24
|
+
<%= link_to "Run test", new_run_path(prompt_id: current.id), class: ck_button_classes(:dark) %>
|
|
25
|
+
</div>
|
|
26
|
+
</section>
|
|
27
|
+
|
|
28
|
+
<section>
|
|
29
|
+
<div class="ck-prompt-preview__header">
|
|
30
|
+
<p class="ck-kicker">Prompt</p>
|
|
31
|
+
<% judged_run = @runs.select { |r| r.prompt_id == @prompt.id && r.status == "completed" }.find { |r| r.responses.joins(:reviews).exists? } %>
|
|
32
|
+
<% if judged_run %>
|
|
33
|
+
<%= button_to "Suggest improvements", suggest_run_path(judged_run), method: :post, class: ck_button_classes(:light, variant: :outline) + " ck-button--sm", form_class: "inline-block" %>
|
|
34
|
+
<% else %>
|
|
35
|
+
<p class="ck-hint"><%= link_to "Run a test with judging configured", new_run_path(prompt_id: @prompt.id), class: "ck-link" %> to get AI-suggested improvements.</p>
|
|
36
|
+
<% end %>
|
|
37
|
+
</div>
|
|
38
|
+
<pre class="ck-code ck-code--dark"><%= @prompt.template %></pre>
|
|
39
|
+
</section>
|
|
40
|
+
|
|
41
|
+
<% versions = @prompt.family_versions %>
|
|
42
|
+
<% if versions.size > 1 %>
|
|
43
|
+
<section class="ck-card--spaced">
|
|
44
|
+
<p class="ck-kicker">Versions</p>
|
|
45
|
+
<table class="ck-results-table" style="margin-top: 0.5rem;">
|
|
46
|
+
<thead>
|
|
47
|
+
<tr>
|
|
48
|
+
<th>Version</th>
|
|
49
|
+
<th>Model</th>
|
|
50
|
+
<th>Created</th>
|
|
51
|
+
<th></th>
|
|
52
|
+
</tr>
|
|
53
|
+
</thead>
|
|
54
|
+
<tbody>
|
|
55
|
+
<% versions.each do |v| %>
|
|
56
|
+
<tr>
|
|
57
|
+
<td><strong>v<%= v.version_number %></strong></td>
|
|
58
|
+
<td><span class="ck-chip ck-chip--soft"><%= v.llm_model %></span></td>
|
|
59
|
+
<td class="ck-meta-copy"><%= time_ago_in_words(v.created_at) %> ago</td>
|
|
60
|
+
<td>
|
|
61
|
+
<% if v.current? %>
|
|
62
|
+
<span class="ck-chip">Current</span>
|
|
63
|
+
<% else %>
|
|
64
|
+
<%= button_to "Publish", publish_prompt_path(v), method: :post, class: "ck-chip ck-chip--publish", form_class: "inline-block" %>
|
|
65
|
+
<% end %>
|
|
66
|
+
</td>
|
|
67
|
+
</tr>
|
|
68
|
+
<% end %>
|
|
69
|
+
</tbody>
|
|
70
|
+
</table>
|
|
71
|
+
</section>
|
|
72
|
+
<% end %>
|
|
73
|
+
|
|
74
|
+
<% if @runs.any? %>
|
|
75
|
+
<section class="ck-card--spaced">
|
|
76
|
+
<p class="ck-kicker">Runs</p>
|
|
77
|
+
|
|
78
|
+
<table class="ck-results-table" style="margin-top: 0.5rem;">
|
|
79
|
+
<thead>
|
|
80
|
+
<tr>
|
|
81
|
+
<th>Run</th>
|
|
82
|
+
<th>Version</th>
|
|
83
|
+
<th>Responses</th>
|
|
84
|
+
<th>Avg score</th>
|
|
85
|
+
<th>Metrics</th>
|
|
86
|
+
<th>When</th>
|
|
87
|
+
<th></th>
|
|
88
|
+
</tr>
|
|
89
|
+
</thead>
|
|
90
|
+
<tbody>
|
|
91
|
+
<% @runs.each do |run| %>
|
|
92
|
+
<tr onclick="window.location='<%= run_path(run) %>'" style="cursor: pointer;">
|
|
93
|
+
<td><span class="ck-run-name"><span class="<%= ck_run_dot(run) %>"></span><strong><%= run.name %></strong></span></td>
|
|
94
|
+
<td><span class="ck-chip ck-chip--soft">v<%= run.prompt.version_number %></span></td>
|
|
95
|
+
<td><%= run.responses.size %></td>
|
|
96
|
+
<td>
|
|
97
|
+
<% avg = run.avg_score %>
|
|
98
|
+
<% if avg %>
|
|
99
|
+
<span class="<%= ck_badge_classes(ck_score_kind(avg)) %>"><%= avg %></span>
|
|
100
|
+
<% else %>
|
|
101
|
+
—
|
|
102
|
+
<% end %>
|
|
103
|
+
</td>
|
|
104
|
+
<td>
|
|
105
|
+
<% metrics = run.metric_averages %>
|
|
106
|
+
<% if metrics.any? %>
|
|
107
|
+
<div class="ck-metric-bar ck-metric-bar--compact">
|
|
108
|
+
<% metrics.each do |m| %>
|
|
109
|
+
<span class="ck-metric-pip ck-metric-pip--<%= ck_score_kind(m[:avg]) %>">
|
|
110
|
+
<span class="ck-metric-pip__bar"></span>
|
|
111
|
+
<span class="ck-metric-pip__label"><%= m[:name] %> <strong><%= m[:avg] %></strong></span>
|
|
112
|
+
</span>
|
|
113
|
+
<% end %>
|
|
114
|
+
</div>
|
|
115
|
+
<% else %>
|
|
116
|
+
—
|
|
117
|
+
<% end %>
|
|
118
|
+
</td>
|
|
119
|
+
<td class="ck-meta-copy"><%= time_ago_in_words(run.created_at) %> ago</td>
|
|
120
|
+
<td class="ck-results-table__arrow">→</td>
|
|
121
|
+
</tr>
|
|
122
|
+
<% end %>
|
|
123
|
+
</tbody>
|
|
124
|
+
</table>
|
|
125
|
+
</section>
|
|
126
|
+
<% end %>
|
|
127
|
+
|
|
128
|
+
<% suggestions = CompletionKit::Suggestion.where(prompt_id: @prompt.family_versions.select(:id)).order(created_at: :desc) %>
|
|
129
|
+
<% if suggestions.any? %>
|
|
130
|
+
<section class="ck-card--spaced">
|
|
131
|
+
<p class="ck-kicker">Suggestions</p>
|
|
132
|
+
<table class="ck-results-table" style="margin-top: 0.5rem;">
|
|
133
|
+
<thead>
|
|
134
|
+
<tr>
|
|
135
|
+
<th>Run</th>
|
|
136
|
+
<th>Reasoning</th>
|
|
137
|
+
<th>Applied</th>
|
|
138
|
+
<th>When</th>
|
|
139
|
+
<th></th>
|
|
140
|
+
</tr>
|
|
141
|
+
</thead>
|
|
142
|
+
<tbody>
|
|
143
|
+
<% suggestions.each do |s| %>
|
|
144
|
+
<tr onclick="window.location='<%= suggestion_run_path(s.run) %>'" style="cursor: pointer;">
|
|
145
|
+
<td><strong><%= s.run.name %></strong></td>
|
|
146
|
+
<td class="ck-meta-copy"><%= truncate(s.reasoning.to_s, length: 100) %></td>
|
|
147
|
+
<td><%= s.applied_at? ? content_tag(:span, "Applied", class: "ck-chip", style: "background: var(--ck-success-soft); color: var(--ck-success);") : "—".html_safe %></td>
|
|
148
|
+
<td class="ck-meta-copy"><%= time_ago_in_words(s.created_at) %> ago</td>
|
|
149
|
+
<td class="ck-results-table__arrow">→</td>
|
|
150
|
+
</tr>
|
|
151
|
+
<% end %>
|
|
152
|
+
</tbody>
|
|
153
|
+
</table>
|
|
154
|
+
</section>
|
|
155
|
+
<% end %>
|
|
156
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<div id="discovery_status_<%= provider_credential.id %>">
|
|
2
|
+
<% if provider_credential.discovery_status == "discovering" %>
|
|
3
|
+
<div class="ck-discovery-bar">
|
|
4
|
+
<div class="ck-discovery-bar__label">
|
|
5
|
+
<% if provider_credential.discovery_total > 0 %>
|
|
6
|
+
Testing models… <%= provider_credential.discovery_current %>/<%= provider_credential.discovery_total %>
|
|
7
|
+
<% else %>
|
|
8
|
+
Fetching model list…
|
|
9
|
+
<% end %>
|
|
10
|
+
</div>
|
|
11
|
+
<% if provider_credential.discovery_total > 0 %>
|
|
12
|
+
<div class="ck-discovery-bar__track">
|
|
13
|
+
<div class="ck-discovery-bar__fill" style="width: <%= (provider_credential.discovery_current.to_f / provider_credential.discovery_total * 100).round %>%"></div>
|
|
14
|
+
</div>
|
|
15
|
+
<% else %>
|
|
16
|
+
<div class="ck-discovery-bar__track">
|
|
17
|
+
<div class="ck-discovery-bar__fill ck-discovery-bar__fill--indeterminate"></div>
|
|
18
|
+
</div>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
21
|
+
<% elsif provider_credential.discovery_status == "failed" %>
|
|
22
|
+
<div class="ck-discovery-bar ck-discovery-bar--failed">
|
|
23
|
+
<div class="ck-discovery-bar__label">Model discovery failed</div>
|
|
24
|
+
</div>
|
|
25
|
+
<% elsif provider_credential.discovery_status == "completed" && local_assigns.fetch(:show_completed, true) %>
|
|
26
|
+
<div class="ck-discovery-bar ck-discovery-bar--completed">
|
|
27
|
+
<div class="ck-discovery-bar__label">Available models list updated <%= time_ago_in_words(provider_credential.updated_at) %> ago</div>
|
|
28
|
+
</div>
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<%= form_with(model: provider_credential, local: true) do |form| %>
|
|
2
|
+
<% if provider_credential.errors.any? %>
|
|
3
|
+
<div class="ck-flash ck-flash--alert">
|
|
4
|
+
<p class="ck-flash__title"><%= pluralize(provider_credential.errors.count, "problem") %> prevented this provider credential from being saved.</p>
|
|
5
|
+
<ul class="ck-error-list">
|
|
6
|
+
<% provider_credential.errors.full_messages.each do |message| %>
|
|
7
|
+
<li><%= message %></li>
|
|
8
|
+
<% end %>
|
|
9
|
+
</ul>
|
|
10
|
+
</div>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<div class="ck-card ck-form-card">
|
|
14
|
+
<div class="ck-field">
|
|
15
|
+
<%= form.label :provider, class: "ck-label" %>
|
|
16
|
+
<%= form.select :provider, CompletionKit::ProviderCredential::PROVIDERS.map { |p| [CompletionKit::ProviderCredential::PROVIDER_LABELS[p] || p.titleize, p] }, {}, { class: "ck-input" } %>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="ck-field">
|
|
20
|
+
<%= form.label :api_key, "API key", class: "ck-label" %>
|
|
21
|
+
<%= form.text_area :api_key, rows: 3, class: "ck-input ck-input--area ck-input--code", placeholder: "Paste the provider API key" %>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="ck-field">
|
|
25
|
+
<%= form.label :api_endpoint, "API endpoint", class: "ck-label" %>
|
|
26
|
+
<%= form.text_field :api_endpoint, class: "ck-input", placeholder: "Only needed for Ollama or custom OpenAI-compatible endpoints" %>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="ck-actions">
|
|
30
|
+
<%= link_to "Cancel", provider_credentials_path, class: ck_button_classes(:light, variant: :outline) %>
|
|
31
|
+
<%= form.submit(provider_credential.persisted? ? "Save provider" : "Create provider", class: ck_button_classes(:dark)) %>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
|
|
37
|
+
<% if provider_credential.persisted? %>
|
|
38
|
+
<%= turbo_stream_from "completion_kit_provider_#{provider_credential.id}" %>
|
|
39
|
+
|
|
40
|
+
<% models = CompletionKit::Model.where(provider: provider_credential.provider).active.order(:model_id) %>
|
|
41
|
+
<% if models.any? || provider_credential.discovery_status.present? %>
|
|
42
|
+
<div class="ck-card ck-form-card__models">
|
|
43
|
+
<div class="ck-form-card__footer-header">
|
|
44
|
+
<%= render "discovery_status", provider_credential: provider_credential %>
|
|
45
|
+
<button type="button" class="ck-icon-btn" title="Refresh models" aria-label="Refresh available models" onclick="fetch('<%= refresh_provider_credential_path(provider_credential) %>', {method:'POST',headers:{'X-CSRF-Token':document.querySelector('meta[name=csrf-token]').content}})">
|
|
46
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" width="14" height="14" aria-hidden="true"><path fill-rule="evenodd" d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.681.75.75 0 0 1-1.264-.808 6 6 0 0 1 9.44-.908l.84.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44.908l-.84-.84v1.68a.75.75 0 0 1-1.5 0V9.567a.75.75 0 0 1 .75-.75h3.182a.75.75 0 0 1 0 1.5h-1.37l.84.841a4.5 4.5 0 0 0 7.08-.681.75.75 0 0 1 1.024-.274Z" clip-rule="evenodd"/></svg>
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<% if models.any? %>
|
|
51
|
+
<details class="ck-model-list-details">
|
|
52
|
+
<summary class="ck-label" style="cursor: pointer; user-select: none;">Available models (<%= models.count %>)</summary>
|
|
53
|
+
<div class="ck-model-list">
|
|
54
|
+
<% models.each do |m| %>
|
|
55
|
+
<span class="ck-model-list__item">
|
|
56
|
+
<%= m.display_name || m.model_id %>
|
|
57
|
+
<% if m.supports_generation && m.supports_judging %>
|
|
58
|
+
<span class="ck-model-list__badge">gen + judge</span>
|
|
59
|
+
<% elsif m.supports_generation %>
|
|
60
|
+
<span class="ck-model-list__badge">gen</span>
|
|
61
|
+
<% elsif m.supports_judging %>
|
|
62
|
+
<span class="ck-model-list__badge">judge</span>
|
|
63
|
+
<% end %>
|
|
64
|
+
</span>
|
|
65
|
+
<% end %>
|
|
66
|
+
</div>
|
|
67
|
+
</details>
|
|
68
|
+
<% end %>
|
|
69
|
+
</div>
|
|
70
|
+
<% end %>
|
|
71
|
+
<% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<ol class="ck-breadcrumb">
|
|
2
|
+
<li><%= link_to "Settings", provider_credentials_path %></li>
|
|
3
|
+
<li>Edit</li>
|
|
4
|
+
</ol>
|
|
5
|
+
|
|
6
|
+
<section class="ck-page-header">
|
|
7
|
+
<div>
|
|
8
|
+
<h1 class="ck-title">Edit provider</h1>
|
|
9
|
+
</div>
|
|
10
|
+
</section>
|
|
11
|
+
|
|
12
|
+
<%= render "form", provider_credential: @provider_credential %>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<% @provider_credentials.each do |pc| %>
|
|
2
|
+
<%= turbo_stream_from "completion_kit_provider_#{pc.id}" %>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<section class="ck-page-header">
|
|
6
|
+
<div>
|
|
7
|
+
<h1 class="ck-title">Settings</h1>
|
|
8
|
+
<p class="ck-lead">API keys for LLM providers. These are used for both generating prompt completions and running judge evaluations.</p>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="ck-actions">
|
|
11
|
+
<%= link_to "New provider", new_provider_credential_path, class: ck_button_classes(:dark) %>
|
|
12
|
+
</div>
|
|
13
|
+
</section>
|
|
14
|
+
|
|
15
|
+
<% if @provider_credentials.any? %>
|
|
16
|
+
<% default_endpoints = {"openai" => "https://api.openai.com", "anthropic" => "https://api.anthropic.com"} %>
|
|
17
|
+
<div class="ck-provider-list">
|
|
18
|
+
<% @provider_credentials.each do |provider_credential| %>
|
|
19
|
+
<% masked_key = provider_credential.api_key.present? ? "#{provider_credential.api_key[0..6]}#{'•' * 12}#{provider_credential.api_key[-4..]}" : "—" %>
|
|
20
|
+
<a href="<%= edit_provider_credential_path(provider_credential) %>" class="ck-provider-card">
|
|
21
|
+
<div class="ck-provider-card__header">
|
|
22
|
+
<span class="ck-provider-card__name"><%= provider_credential.display_provider %></span>
|
|
23
|
+
<span class="ck-provider-card__key"><%= masked_key %></span>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="ck-provider-card__meta">
|
|
27
|
+
<span><%= provider_credential.api_endpoint.presence || default_endpoints[provider_credential.provider] %></span>
|
|
28
|
+
<span><%= provider_credential.prompt_count %> prompts</span>
|
|
29
|
+
<span><%= provider_credential.judge_count %> judges</span>
|
|
30
|
+
<span><%= provider_credential.last_used_at ? "Used #{time_ago_in_words(provider_credential.last_used_at)} ago" : "Never used" %></span>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<%= render "discovery_status", provider_credential: provider_credential %>
|
|
34
|
+
</a>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
37
|
+
<% else %>
|
|
38
|
+
<div class="ck-empty">
|
|
39
|
+
No providers yet. Add one to start running prompts.
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<ol class="ck-breadcrumb">
|
|
2
|
+
<li><%= link_to "Settings", provider_credentials_path %></li>
|
|
3
|
+
<li>New</li>
|
|
4
|
+
</ol>
|
|
5
|
+
|
|
6
|
+
<section class="ck-page-header">
|
|
7
|
+
<div>
|
|
8
|
+
<h1 class="ck-title">New provider</h1>
|
|
9
|
+
</div>
|
|
10
|
+
</section>
|
|
11
|
+
|
|
12
|
+
<%= render "form", provider_credential: @provider_credential %>
|