completion-kit 0.5.3 → 0.5.5
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 +4 -4
- data/app/assets/stylesheets/completion_kit/application.css +375 -0
- data/app/controllers/completion_kit/onboarding_controller.rb +23 -0
- data/app/helpers/completion_kit/application_helper.rb +6 -1
- data/app/jobs/completion_kit/judge_review_job.rb +10 -0
- data/app/models/completion_kit/model.rb +4 -1
- data/app/services/completion_kit/api_config.rb +3 -1
- data/app/services/completion_kit/model_discovery_service.rb +71 -43
- data/app/services/completion_kit/onboarding/checklist.rb +66 -0
- data/app/services/completion_kit/onboarding/sample_data.rb +37 -0
- data/app/views/completion_kit/onboarding/show.html.erb +89 -0
- data/app/views/completion_kit/prompts/show.html.erb +1 -1
- data/app/views/completion_kit/provider_credentials/_models_card.html.erb +4 -2
- data/app/views/completion_kit/runs/show.html.erb +1 -1
- data/app/views/layouts/completion_kit/application.html.erb +2 -1
- data/config/routes.rb +5 -1
- data/lib/completion_kit/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89a2dd49e9edb75dada51386769cfd7e16f596c3b30c434588864487ab801d5b
|
|
4
|
+
data.tar.gz: df8d9acfd4ef8aeab73508e7cf9adb2c083c7b9ef3d1c4304e1a3e01723b17e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b03af38565cb43b7a57747896136817d954b06a89ba4551afb314ca93cd56a70e9d3068271d4f522741efa7d830b106a3a39e05997ea38fc3fb65996741ad08
|
|
7
|
+
data.tar.gz: be0dad1d98c850854770351edbfdcd7867fbda7b6590634bae2997707a136a94cee066c79fd2ae8a259ea9c01db06a89b7088012a072655c7c627f7c475a801a
|
|
@@ -494,10 +494,18 @@ tr:hover .ck-chip--publish {
|
|
|
494
494
|
letter-spacing: 0.04em;
|
|
495
495
|
text-transform: uppercase;
|
|
496
496
|
line-height: 1;
|
|
497
|
+
white-space: nowrap;
|
|
497
498
|
cursor: pointer;
|
|
498
499
|
transition: all 0.15s ease;
|
|
499
500
|
}
|
|
500
501
|
|
|
502
|
+
.ck-magic-icon {
|
|
503
|
+
width: 1.1em;
|
|
504
|
+
height: 1.1em;
|
|
505
|
+
flex-shrink: 0;
|
|
506
|
+
color: var(--ck-warning);
|
|
507
|
+
}
|
|
508
|
+
|
|
501
509
|
.ck-button:hover {
|
|
502
510
|
transform: translateY(-1px);
|
|
503
511
|
}
|
|
@@ -1140,6 +1148,22 @@ tr:hover .ck-chip--publish {
|
|
|
1140
1148
|
font-size: 0.85rem;
|
|
1141
1149
|
}
|
|
1142
1150
|
|
|
1151
|
+
.ck-model-table__unknown {
|
|
1152
|
+
display: inline-flex;
|
|
1153
|
+
align-items: center;
|
|
1154
|
+
justify-content: center;
|
|
1155
|
+
width: 1.25rem;
|
|
1156
|
+
height: 1.25rem;
|
|
1157
|
+
border-radius: 50%;
|
|
1158
|
+
background: rgba(148, 163, 184, 0.12);
|
|
1159
|
+
color: var(--ck-muted);
|
|
1160
|
+
font-family: var(--ck-mono);
|
|
1161
|
+
font-size: 0.78rem;
|
|
1162
|
+
font-weight: 700;
|
|
1163
|
+
line-height: 1;
|
|
1164
|
+
cursor: help;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1143
1167
|
.ck-model-list__summary {
|
|
1144
1168
|
display: flex;
|
|
1145
1169
|
align-items: center;
|
|
@@ -3779,3 +3803,354 @@ a.tag-mark {
|
|
|
3779
3803
|
.ck-tag-filter__clear:hover { color: var(--ck-accent); }
|
|
3780
3804
|
|
|
3781
3805
|
|
|
3806
|
+
/* --- Onboarding / launch checklist --- */
|
|
3807
|
+
@keyframes ck-launch-rise {
|
|
3808
|
+
from { opacity: 0; transform: translateY(14px); }
|
|
3809
|
+
to { opacity: 1; transform: translateY(0); }
|
|
3810
|
+
}
|
|
3811
|
+
|
|
3812
|
+
.ck-launch {
|
|
3813
|
+
position: relative;
|
|
3814
|
+
max-width: 720px;
|
|
3815
|
+
margin: 1rem auto 4rem;
|
|
3816
|
+
padding-top: 1rem;
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
.ck-launch__field {
|
|
3820
|
+
position: absolute;
|
|
3821
|
+
inset: -2rem -3rem auto -3rem;
|
|
3822
|
+
height: 300px;
|
|
3823
|
+
pointer-events: none;
|
|
3824
|
+
z-index: 0;
|
|
3825
|
+
background:
|
|
3826
|
+
radial-gradient(ellipse 70% 80% at 50% 0%, var(--ck-accent-soft) 0%, transparent 65%),
|
|
3827
|
+
repeating-linear-gradient(90deg, transparent 0 23px, rgba(148, 163, 184, 0.035) 23px 24px),
|
|
3828
|
+
repeating-linear-gradient(0deg, transparent 0 23px, rgba(148, 163, 184, 0.035) 23px 24px);
|
|
3829
|
+
-webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
|
|
3830
|
+
mask-image: linear-gradient(to bottom, #000 0%, transparent 100%);
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
.ck-launch__header {
|
|
3834
|
+
position: relative;
|
|
3835
|
+
z-index: 1;
|
|
3836
|
+
text-align: center;
|
|
3837
|
+
margin-bottom: 2.25rem;
|
|
3838
|
+
animation: ck-launch-rise 0.5s ease both;
|
|
3839
|
+
}
|
|
3840
|
+
|
|
3841
|
+
.ck-launch__overline {
|
|
3842
|
+
margin: 0 0 0.6rem;
|
|
3843
|
+
font-family: var(--ck-mono);
|
|
3844
|
+
font-size: 0.72rem;
|
|
3845
|
+
letter-spacing: 0.32em;
|
|
3846
|
+
text-transform: uppercase;
|
|
3847
|
+
color: var(--ck-accent);
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
.ck-launch__org {
|
|
3851
|
+
margin: 0;
|
|
3852
|
+
font-family: var(--ck-mono);
|
|
3853
|
+
font-size: clamp(1.8rem, 4vw, 2.6rem);
|
|
3854
|
+
font-weight: 600;
|
|
3855
|
+
letter-spacing: -0.02em;
|
|
3856
|
+
color: var(--ck-text);
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
.ck-launch__handle {
|
|
3860
|
+
margin: 0.5rem 0 0;
|
|
3861
|
+
font-family: var(--ck-mono);
|
|
3862
|
+
font-size: 0.8rem;
|
|
3863
|
+
letter-spacing: 0.04em;
|
|
3864
|
+
color: var(--ck-muted);
|
|
3865
|
+
}
|
|
3866
|
+
|
|
3867
|
+
.ck-launch__panel {
|
|
3868
|
+
position: relative;
|
|
3869
|
+
z-index: 1;
|
|
3870
|
+
background: var(--ck-surface);
|
|
3871
|
+
border: 1px solid var(--ck-line);
|
|
3872
|
+
border-radius: var(--ck-radius-lg);
|
|
3873
|
+
padding: 1.75rem 2rem 2rem;
|
|
3874
|
+
box-shadow: 0 24px 64px -28px rgba(0, 0, 0, 0.5);
|
|
3875
|
+
animation: ck-launch-rise 0.5s ease both;
|
|
3876
|
+
animation-delay: 0.08s;
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
.ck-launch__progress {
|
|
3880
|
+
padding-bottom: 1.5rem;
|
|
3881
|
+
margin-bottom: 0.5rem;
|
|
3882
|
+
border-bottom: 1px solid var(--ck-line);
|
|
3883
|
+
}
|
|
3884
|
+
.ck-launch__progress-head {
|
|
3885
|
+
display: flex;
|
|
3886
|
+
align-items: baseline;
|
|
3887
|
+
justify-content: space-between;
|
|
3888
|
+
gap: 1rem;
|
|
3889
|
+
margin-bottom: 0.85rem;
|
|
3890
|
+
}
|
|
3891
|
+
.ck-launch__progress-title {
|
|
3892
|
+
margin: 0;
|
|
3893
|
+
font-family: var(--ck-mono);
|
|
3894
|
+
font-size: 1.05rem;
|
|
3895
|
+
font-weight: 600;
|
|
3896
|
+
letter-spacing: -0.02em;
|
|
3897
|
+
color: var(--ck-text);
|
|
3898
|
+
}
|
|
3899
|
+
.ck-launch__progress-count {
|
|
3900
|
+
margin: 0;
|
|
3901
|
+
font-family: var(--ck-mono);
|
|
3902
|
+
font-size: 0.78rem;
|
|
3903
|
+
letter-spacing: 0.02em;
|
|
3904
|
+
color: var(--ck-muted);
|
|
3905
|
+
white-space: nowrap;
|
|
3906
|
+
}
|
|
3907
|
+
.ck-launch__progress-bar {
|
|
3908
|
+
height: 5px;
|
|
3909
|
+
border-radius: 999px;
|
|
3910
|
+
background: var(--ck-line);
|
|
3911
|
+
overflow: hidden;
|
|
3912
|
+
}
|
|
3913
|
+
.ck-launch__progress-fill {
|
|
3914
|
+
display: block;
|
|
3915
|
+
height: 100%;
|
|
3916
|
+
border-radius: 999px;
|
|
3917
|
+
background: var(--ck-accent);
|
|
3918
|
+
box-shadow: 0 0 10px var(--ck-accent-soft);
|
|
3919
|
+
transition: width 0.4s ease;
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
.ck-launch__steps {
|
|
3923
|
+
list-style: none;
|
|
3924
|
+
margin: 0;
|
|
3925
|
+
padding: 0;
|
|
3926
|
+
display: flex;
|
|
3927
|
+
flex-direction: column;
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
.ck-launch__step {
|
|
3931
|
+
display: grid;
|
|
3932
|
+
grid-template-columns: 2.4rem 1fr;
|
|
3933
|
+
gap: 1rem;
|
|
3934
|
+
align-items: baseline;
|
|
3935
|
+
padding: 1.25rem 0 1.25rem 1rem;
|
|
3936
|
+
border-top: 1px solid var(--ck-line);
|
|
3937
|
+
border-left: 2px solid transparent;
|
|
3938
|
+
animation: ck-launch-rise 0.45s ease both;
|
|
3939
|
+
animation-delay: calc(0.24s + var(--i) * 0.07s);
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
.ck-launch__step-num {
|
|
3943
|
+
font-family: var(--ck-mono);
|
|
3944
|
+
font-size: 0.8rem;
|
|
3945
|
+
font-weight: 600;
|
|
3946
|
+
letter-spacing: 0.04em;
|
|
3947
|
+
color: var(--ck-dim);
|
|
3948
|
+
user-select: none;
|
|
3949
|
+
white-space: nowrap;
|
|
3950
|
+
}
|
|
3951
|
+
.ck-launch__step--done .ck-launch__step-num { color: var(--ck-success); }
|
|
3952
|
+
.ck-launch__step--next .ck-launch__step-num { color: var(--ck-accent); }
|
|
3953
|
+
.ck-launch__step-tick { margin-left: 0.3em; font-size: 0.78em; }
|
|
3954
|
+
|
|
3955
|
+
.ck-launch__step--done { border-left-color: rgba(45, 212, 168, 0.4); }
|
|
3956
|
+
|
|
3957
|
+
.ck-launch__step-body { min-width: 0; }
|
|
3958
|
+
|
|
3959
|
+
.ck-launch__step-head {
|
|
3960
|
+
display: flex;
|
|
3961
|
+
align-items: baseline;
|
|
3962
|
+
gap: 0.7rem;
|
|
3963
|
+
flex-wrap: wrap;
|
|
3964
|
+
margin-bottom: 0.3rem;
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
.ck-launch__step-title {
|
|
3968
|
+
margin: 0;
|
|
3969
|
+
font-size: 1rem;
|
|
3970
|
+
font-weight: 500;
|
|
3971
|
+
letter-spacing: -0.005em;
|
|
3972
|
+
}
|
|
3973
|
+
.ck-launch__step-title a { color: var(--ck-text); text-decoration: none; }
|
|
3974
|
+
.ck-launch__step-title a:hover { color: var(--ck-accent); }
|
|
3975
|
+
.ck-launch__step--done .ck-launch__step-title { color: var(--ck-muted); }
|
|
3976
|
+
|
|
3977
|
+
.ck-launch__step-tag {
|
|
3978
|
+
font-family: var(--ck-mono);
|
|
3979
|
+
font-size: 0.6rem;
|
|
3980
|
+
letter-spacing: 0.18em;
|
|
3981
|
+
text-transform: uppercase;
|
|
3982
|
+
color: var(--ck-accent);
|
|
3983
|
+
padding: 0.12rem 0.45rem;
|
|
3984
|
+
border: 1px solid var(--ck-accent);
|
|
3985
|
+
border-radius: 999px;
|
|
3986
|
+
line-height: 1.4;
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
.ck-launch__step-desc {
|
|
3990
|
+
margin: 0;
|
|
3991
|
+
font-size: 0.9rem;
|
|
3992
|
+
line-height: 1.55;
|
|
3993
|
+
color: var(--ck-muted);
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
.ck-launch__step--pending .ck-launch__step-title,
|
|
3997
|
+
.ck-launch__step--pending .ck-launch__step-desc { opacity: 0.65; }
|
|
3998
|
+
|
|
3999
|
+
.ck-launch__step--next {
|
|
4000
|
+
border-left-color: var(--ck-accent);
|
|
4001
|
+
background: linear-gradient(90deg, var(--ck-accent-soft) 0%, transparent 30%);
|
|
4002
|
+
margin-left: -1rem;
|
|
4003
|
+
padding-left: 2rem;
|
|
4004
|
+
padding-right: 1rem;
|
|
4005
|
+
border-radius: 0 var(--ck-radius) var(--ck-radius) 0;
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
.ck-launch__step-cta {
|
|
4009
|
+
display: inline-flex;
|
|
4010
|
+
align-items: center;
|
|
4011
|
+
gap: 0.4rem;
|
|
4012
|
+
margin-top: 0.85rem;
|
|
4013
|
+
padding: 0.5rem 0.95rem;
|
|
4014
|
+
background: var(--ck-accent);
|
|
4015
|
+
color: #06121a;
|
|
4016
|
+
border-radius: var(--ck-radius);
|
|
4017
|
+
font-family: var(--ck-mono);
|
|
4018
|
+
font-size: 0.75rem;
|
|
4019
|
+
letter-spacing: 0.06em;
|
|
4020
|
+
text-transform: uppercase;
|
|
4021
|
+
font-weight: 600;
|
|
4022
|
+
text-decoration: none;
|
|
4023
|
+
transition: filter 0.15s, transform 0.15s;
|
|
4024
|
+
}
|
|
4025
|
+
.ck-launch__step-cta:hover { filter: brightness(1.1); transform: translateY(-1px); }
|
|
4026
|
+
|
|
4027
|
+
/* Opt-in starter data — quiet, secondary (bright cyan is reserved for the next-step CTA) */
|
|
4028
|
+
.ck-launch__sample {
|
|
4029
|
+
margin-top: 1.5rem;
|
|
4030
|
+
padding: 1rem 1.1rem;
|
|
4031
|
+
display: flex;
|
|
4032
|
+
flex-wrap: wrap;
|
|
4033
|
+
align-items: center;
|
|
4034
|
+
justify-content: space-between;
|
|
4035
|
+
gap: 0.85rem 1.25rem;
|
|
4036
|
+
border: 1px dashed var(--ck-line);
|
|
4037
|
+
border-radius: var(--ck-radius);
|
|
4038
|
+
background: var(--ck-bg-strong);
|
|
4039
|
+
}
|
|
4040
|
+
.ck-launch__sample-copy {
|
|
4041
|
+
margin: 0;
|
|
4042
|
+
flex: 1 1 18rem;
|
|
4043
|
+
font-size: 0.85rem;
|
|
4044
|
+
line-height: 1.55;
|
|
4045
|
+
color: var(--ck-muted);
|
|
4046
|
+
}
|
|
4047
|
+
.ck-launch__sample-copy code { font-family: var(--ck-mono); font-size: 0.85em; color: var(--ck-text); }
|
|
4048
|
+
.ck-launch__sample-cta {
|
|
4049
|
+
display: inline-flex;
|
|
4050
|
+
align-items: center;
|
|
4051
|
+
gap: 0.4rem;
|
|
4052
|
+
flex-shrink: 0;
|
|
4053
|
+
margin: 0;
|
|
4054
|
+
padding: 0.5rem 0.95rem;
|
|
4055
|
+
background: transparent;
|
|
4056
|
+
border: 1px solid var(--ck-line);
|
|
4057
|
+
color: var(--ck-text);
|
|
4058
|
+
border-radius: var(--ck-radius);
|
|
4059
|
+
font-family: var(--ck-mono);
|
|
4060
|
+
font-size: 0.72rem;
|
|
4061
|
+
letter-spacing: 0.06em;
|
|
4062
|
+
text-transform: uppercase;
|
|
4063
|
+
font-weight: 600;
|
|
4064
|
+
cursor: pointer;
|
|
4065
|
+
transition: border-color 0.15s, color 0.15s, background 0.15s;
|
|
4066
|
+
}
|
|
4067
|
+
.ck-launch__sample-cta:hover { border-color: var(--ck-accent); color: var(--ck-accent); background: var(--ck-accent-soft); }
|
|
4068
|
+
|
|
4069
|
+
.ck-launch__panel-footer {
|
|
4070
|
+
margin-top: 1.75rem;
|
|
4071
|
+
padding-top: 1.25rem;
|
|
4072
|
+
border-top: 1px solid var(--ck-line);
|
|
4073
|
+
}
|
|
4074
|
+
.ck-launch__dismiss {
|
|
4075
|
+
margin: 0;
|
|
4076
|
+
padding: 0;
|
|
4077
|
+
background: transparent;
|
|
4078
|
+
border: none;
|
|
4079
|
+
color: var(--ck-muted);
|
|
4080
|
+
font-family: var(--ck-mono);
|
|
4081
|
+
font-size: 0.78rem;
|
|
4082
|
+
letter-spacing: 0.04em;
|
|
4083
|
+
cursor: pointer;
|
|
4084
|
+
text-decoration: underline;
|
|
4085
|
+
text-underline-offset: 3px;
|
|
4086
|
+
transition: color 0.15s;
|
|
4087
|
+
}
|
|
4088
|
+
.ck-launch__dismiss:hover { color: var(--ck-accent); }
|
|
4089
|
+
.ck-launch__dismiss-hint {
|
|
4090
|
+
margin: 0.5rem 0 0;
|
|
4091
|
+
font-size: 0.78rem;
|
|
4092
|
+
color: var(--ck-dim);
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
.ck-launch__ready-panel {
|
|
4096
|
+
position: relative;
|
|
4097
|
+
z-index: 1;
|
|
4098
|
+
background: var(--ck-surface);
|
|
4099
|
+
border: 1px solid var(--ck-line);
|
|
4100
|
+
border-radius: var(--ck-radius-lg);
|
|
4101
|
+
padding: 2rem;
|
|
4102
|
+
text-align: center;
|
|
4103
|
+
animation: ck-launch-rise 0.5s ease both;
|
|
4104
|
+
animation-delay: 0.08s;
|
|
4105
|
+
}
|
|
4106
|
+
|
|
4107
|
+
.ck-launch__ready-flag {
|
|
4108
|
+
margin: 0;
|
|
4109
|
+
font-family: var(--ck-mono);
|
|
4110
|
+
font-size: 0.85rem;
|
|
4111
|
+
letter-spacing: 0.14em;
|
|
4112
|
+
text-transform: uppercase;
|
|
4113
|
+
color: var(--ck-success);
|
|
4114
|
+
display: inline-flex;
|
|
4115
|
+
align-items: center;
|
|
4116
|
+
gap: 0.6rem;
|
|
4117
|
+
}
|
|
4118
|
+
.ck-launch__ready-dot {
|
|
4119
|
+
width: 8px;
|
|
4120
|
+
height: 8px;
|
|
4121
|
+
border-radius: 50%;
|
|
4122
|
+
background: var(--ck-success);
|
|
4123
|
+
box-shadow: 0 0 12px var(--ck-success);
|
|
4124
|
+
}
|
|
4125
|
+
.ck-launch__ready-copy { margin: 0.6rem 0 1.5rem; color: var(--ck-muted); }
|
|
4126
|
+
|
|
4127
|
+
.ck-launch__quicklinks {
|
|
4128
|
+
display: flex;
|
|
4129
|
+
gap: 0.6rem;
|
|
4130
|
+
justify-content: center;
|
|
4131
|
+
flex-wrap: wrap;
|
|
4132
|
+
}
|
|
4133
|
+
.ck-launch__quicklink {
|
|
4134
|
+
padding: 0.5rem 0.95rem;
|
|
4135
|
+
border: 1px solid var(--ck-line-strong);
|
|
4136
|
+
border-radius: var(--ck-radius);
|
|
4137
|
+
font-family: var(--ck-mono);
|
|
4138
|
+
font-size: 0.75rem;
|
|
4139
|
+
letter-spacing: 0.06em;
|
|
4140
|
+
text-transform: uppercase;
|
|
4141
|
+
color: var(--ck-text);
|
|
4142
|
+
text-decoration: none;
|
|
4143
|
+
transition: border-color 0.15s, background 0.15s;
|
|
4144
|
+
}
|
|
4145
|
+
.ck-launch__quicklink:hover { border-color: var(--ck-accent); background: var(--ck-surface-hover); }
|
|
4146
|
+
|
|
4147
|
+
@media (max-width: 560px) {
|
|
4148
|
+
.ck-launch__panel { padding: 1.5rem 1.25rem; }
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
@media (prefers-reduced-motion: reduce) {
|
|
4152
|
+
.ck-launch__header,
|
|
4153
|
+
.ck-launch__panel,
|
|
4154
|
+
.ck-launch__ready-panel,
|
|
4155
|
+
.ck-launch__step { animation: none; }
|
|
4156
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module CompletionKit
|
|
2
|
+
class OnboardingController < ApplicationController
|
|
3
|
+
DISMISS_COOKIE = :ck_onboarding_dismissed
|
|
4
|
+
|
|
5
|
+
def show
|
|
6
|
+
cookies.delete(DISMISS_COOKIE) if params[:reset]
|
|
7
|
+
@checklist = Onboarding::Checklist.new
|
|
8
|
+
return if params[:reset]
|
|
9
|
+
|
|
10
|
+
redirect_to prompts_path if @checklist.complete? || cookies[DISMISS_COOKIE]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def dismiss
|
|
14
|
+
cookies[DISMISS_COOKIE] = { value: "1", expires: 1.year.from_now, httponly: true }
|
|
15
|
+
redirect_to prompts_path, notice: "Setup skipped. Pick it back up from Settings → Getting started any time."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def sample_data
|
|
19
|
+
Onboarding::SampleData.install!
|
|
20
|
+
redirect_to onboarding_path, notice: "Loaded a sample dataset and prompt — edit or delete them whenever."
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -72,6 +72,11 @@ module CompletionKit
|
|
|
72
72
|
CompletionKit::ProviderCredential::PROVIDER_LABELS[provider.to_s] || provider.to_s.titleize
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
def ck_model_option_label(model)
|
|
76
|
+
return "#{model[:name]} (?)" if model.key?(:judging_confirmed) && !model[:judging_confirmed]
|
|
77
|
+
model[:name]
|
|
78
|
+
end
|
|
79
|
+
|
|
75
80
|
def ck_grouped_models(models, selected = nil)
|
|
76
81
|
if selected.present? && models.none? { |m| m[:id] == selected }
|
|
77
82
|
retired = CompletionKit::Model.find_by(model_id: selected)
|
|
@@ -90,7 +95,7 @@ module CompletionKit
|
|
|
90
95
|
end
|
|
91
96
|
|
|
92
97
|
ordered_keys = groups.keys.sort_by { |label| [label.start_with?("OpenRouter") ? 1 : 0, label] }
|
|
93
|
-
grouped = ordered_keys.map { |label| [label, groups[label].map { |m| [m
|
|
98
|
+
grouped = ordered_keys.map { |label| [label, groups[label].map { |m| [ck_model_option_label(m), m[:id]] }] }
|
|
94
99
|
grouped_options_for_select(grouped, selected)
|
|
95
100
|
end
|
|
96
101
|
|
|
@@ -71,6 +71,7 @@ module CompletionKit
|
|
|
71
71
|
)
|
|
72
72
|
review.save!
|
|
73
73
|
|
|
74
|
+
confirm_judging_capability(run.judge_model)
|
|
74
75
|
run.send(:broadcast_response_update, response)
|
|
75
76
|
run.send(:broadcast_progress)
|
|
76
77
|
enqueue_completion_check
|
|
@@ -78,6 +79,15 @@ module CompletionKit
|
|
|
78
79
|
|
|
79
80
|
private
|
|
80
81
|
|
|
82
|
+
# A model with supports_judging == nil ("untested") just produced a valid
|
|
83
|
+
# review — promote it to confirmed. No-op once confirmed (so repeated runs
|
|
84
|
+
# don't churn the row), and a model already flagged as a bad judge stays so.
|
|
85
|
+
def confirm_judging_capability(judge_model_id)
|
|
86
|
+
model = Model.find_by(provider: ApiConfig.provider_for_model(judge_model_id), model_id: judge_model_id)
|
|
87
|
+
return unless model && model.supports_judging.nil?
|
|
88
|
+
model.update_columns(supports_judging: true, judging_error: nil)
|
|
89
|
+
end
|
|
90
|
+
|
|
81
91
|
def record_terminal_failure!(error)
|
|
82
92
|
response_id = @response_id || arguments.first
|
|
83
93
|
metric_id = @metric_id || arguments.last
|
|
@@ -8,6 +8,9 @@ module CompletionKit
|
|
|
8
8
|
|
|
9
9
|
scope :active, -> { where(status: "active") }
|
|
10
10
|
scope :for_generation, -> { active.where(supports_generation: true) }
|
|
11
|
-
|
|
11
|
+
# Includes models not yet confirmed as judges (supports_judging: nil) — worth
|
|
12
|
+
# a try, and a successful run flips them to confirmed. Only models known to be
|
|
13
|
+
# bad judges (false) are excluded.
|
|
14
|
+
scope :for_judging, -> { active.where(supports_judging: [true, nil]) }
|
|
12
15
|
end
|
|
13
16
|
end
|
|
@@ -61,7 +61,9 @@ module CompletionKit
|
|
|
61
61
|
end
|
|
62
62
|
query = query.where(provider: provider) if provider.present?
|
|
63
63
|
models = query.order(:provider, :display_name).map do |m|
|
|
64
|
-
{ id: m.model_id, name: m.display_name || m.model_id, provider: m.provider }
|
|
64
|
+
entry = { id: m.model_id, name: m.display_name || m.model_id, provider: m.provider }
|
|
65
|
+
entry[:judging_confirmed] = !m.supports_judging.nil? if scope == :judging
|
|
66
|
+
entry
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
return models if models.any?
|
|
@@ -13,8 +13,13 @@ module CompletionKit
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def refresh!(&on_progress)
|
|
16
|
-
|
|
17
|
-
reconcile(
|
|
16
|
+
discovered = fetch_models
|
|
17
|
+
reconcile(discovered)
|
|
18
|
+
# OpenRouter publishes capability metadata (output modalities, etc.), so we
|
|
19
|
+
# derive everything from the model list and skip live probing entirely.
|
|
20
|
+
# Judging stays unknown ("?") until a real run proves it.
|
|
21
|
+
return if @provider == "openrouter"
|
|
22
|
+
|
|
18
23
|
probe_new_models(&on_progress)
|
|
19
24
|
end
|
|
20
25
|
|
|
@@ -86,10 +91,19 @@ module CompletionKit
|
|
|
86
91
|
next nil if entry["deprecated"] == true
|
|
87
92
|
context_length = entry["context_length"].to_i
|
|
88
93
|
next nil if context_length < 8192
|
|
89
|
-
{ id: entry["id"], display_name: entry["name"] }
|
|
94
|
+
{ id: entry["id"], display_name: entry["name"], supports_generation: openrouter_text_output?(entry) }
|
|
90
95
|
end
|
|
91
96
|
end
|
|
92
97
|
|
|
98
|
+
# OpenRouter exposes architecture.output_modalities (e.g. ["text"], ["image"],
|
|
99
|
+
# ["text", "image"]). A model can be used for generation/judging only if it
|
|
100
|
+
# outputs text. When the field is missing we keep the historical default of
|
|
101
|
+
# treating the model as text-capable.
|
|
102
|
+
def openrouter_text_output?(entry)
|
|
103
|
+
modalities = Array(entry.dig("architecture", "output_modalities")).map(&:to_s)
|
|
104
|
+
modalities.empty? || modalities.include?("text")
|
|
105
|
+
end
|
|
106
|
+
|
|
93
107
|
def fetch_ollama_models
|
|
94
108
|
raise DiscoveryError, "Ollama endpoint URL is required" if @api_endpoint.blank?
|
|
95
109
|
base_url = @api_endpoint.to_s.delete_suffix("/")
|
|
@@ -100,35 +114,67 @@ module CompletionKit
|
|
|
100
114
|
JSON.parse(response.body).fetch("data", []).map { |e| { id: e["id"], display_name: e["id"] } }
|
|
101
115
|
end
|
|
102
116
|
|
|
103
|
-
def reconcile(
|
|
104
|
-
api_model_ids =
|
|
105
|
-
|
|
117
|
+
def reconcile(discovered)
|
|
118
|
+
api_model_ids = discovered.map { |m| m[:id] }
|
|
119
|
+
meta_by_id = discovered.index_by { |m| m[:id] }
|
|
106
120
|
existing = Model.where(provider: @provider).index_by(&:model_id)
|
|
107
121
|
|
|
108
122
|
api_model_ids.each do |model_id|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
existing[model_id].update!(attrs) if existing[model_id].status == "retired" || names_by_id[model_id].present?
|
|
123
|
+
meta = meta_by_id[model_id]
|
|
124
|
+
if (model = existing[model_id])
|
|
125
|
+
reconcile_existing_model(model, meta)
|
|
113
126
|
else
|
|
114
|
-
|
|
115
|
-
provider: @provider,
|
|
116
|
-
model_id: model_id,
|
|
117
|
-
display_name: names_by_id[model_id],
|
|
118
|
-
status: "active",
|
|
119
|
-
discovered_at: Time.current
|
|
120
|
-
}
|
|
121
|
-
if %w[openrouter ollama].include?(@provider)
|
|
122
|
-
attrs[:supports_generation] = true
|
|
123
|
-
attrs[:probed_at] = nil
|
|
124
|
-
end
|
|
125
|
-
Model.create!(attrs)
|
|
127
|
+
Model.create!(new_model_attrs(model_id, meta))
|
|
126
128
|
end
|
|
127
129
|
end
|
|
128
130
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
Model.where(provider: @provider, status: "active")
|
|
132
|
+
.where.not(model_id: api_model_ids)
|
|
133
|
+
.update_all(status: "retired", retired_at: Time.current)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def new_model_attrs(model_id, meta)
|
|
137
|
+
attrs = {
|
|
138
|
+
provider: @provider,
|
|
139
|
+
model_id: model_id,
|
|
140
|
+
display_name: meta[:display_name],
|
|
141
|
+
status: "active",
|
|
142
|
+
discovered_at: Time.current
|
|
143
|
+
}
|
|
144
|
+
if @provider == "openrouter"
|
|
145
|
+
supports_generation = meta[:supports_generation] != false
|
|
146
|
+
attrs.merge!(
|
|
147
|
+
supports_generation: supports_generation,
|
|
148
|
+
supports_judging: nil,
|
|
149
|
+
probed_at: Time.current,
|
|
150
|
+
status: supports_generation ? "active" : "failed"
|
|
151
|
+
)
|
|
152
|
+
elsif @provider == "ollama"
|
|
153
|
+
attrs[:supports_generation] = true
|
|
154
|
+
attrs[:probed_at] = nil
|
|
155
|
+
end
|
|
156
|
+
attrs
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def reconcile_existing_model(model, meta)
|
|
160
|
+
if @provider == "openrouter"
|
|
161
|
+
# Re-derive generation capability from the published metadata every refresh
|
|
162
|
+
# (fixes models discovered before capability metadata was used). Leave
|
|
163
|
+
# supports_judging alone — it's "learned" from successful runs.
|
|
164
|
+
supports_generation = meta[:supports_generation] != false
|
|
165
|
+
model.update!(
|
|
166
|
+
display_name: meta[:display_name].presence || model.display_name,
|
|
167
|
+
supports_generation: supports_generation,
|
|
168
|
+
generation_error: nil,
|
|
169
|
+
probed_at: Time.current,
|
|
170
|
+
status: supports_generation ? "active" : "failed",
|
|
171
|
+
retired_at: nil
|
|
172
|
+
)
|
|
173
|
+
else
|
|
174
|
+
attrs = { status: "active", retired_at: nil }
|
|
175
|
+
attrs[:display_name] = meta[:display_name] if meta[:display_name].present?
|
|
176
|
+
model.update!(attrs) if model.status == "retired" || meta[:display_name].present?
|
|
177
|
+
end
|
|
132
178
|
end
|
|
133
179
|
|
|
134
180
|
def probe_new_models(&on_progress)
|
|
@@ -223,7 +269,6 @@ module CompletionKit
|
|
|
223
269
|
case @provider
|
|
224
270
|
when "openai" then openai_probe(model_id, input, max_tokens)
|
|
225
271
|
when "anthropic" then anthropic_probe(model_id, input, max_tokens)
|
|
226
|
-
when "openrouter" then openrouter_probe(model_id, input, max_tokens)
|
|
227
272
|
when "ollama" then ollama_probe(model_id, input, max_tokens)
|
|
228
273
|
else raise ArgumentError, "Unsupported probe provider: #{@provider}"
|
|
229
274
|
end
|
|
@@ -290,23 +335,6 @@ module CompletionKit
|
|
|
290
335
|
end
|
|
291
336
|
end
|
|
292
337
|
|
|
293
|
-
def openrouter_probe(model_id, input, max_tokens)
|
|
294
|
-
conn = Faraday.new(url: "https://openrouter.ai") do |f|
|
|
295
|
-
f.options.timeout = 30
|
|
296
|
-
f.options.open_timeout = 5
|
|
297
|
-
f.request :retry, max: 1, interval: 0.5
|
|
298
|
-
f.adapter Faraday.default_adapter
|
|
299
|
-
end
|
|
300
|
-
conn.post do |req|
|
|
301
|
-
req.url "/api/v1/chat/completions"
|
|
302
|
-
req.headers["Content-Type"] = "application/json"
|
|
303
|
-
req.headers["Authorization"] = "Bearer #{@api_key}"
|
|
304
|
-
req.headers["HTTP-Referer"] = "https://completionkit.com"
|
|
305
|
-
req.headers["X-Title"] = "CompletionKit"
|
|
306
|
-
req.body = { model: model_id, messages: [{ role: "user", content: input }], max_tokens: max_tokens }.to_json
|
|
307
|
-
end
|
|
308
|
-
end
|
|
309
|
-
|
|
310
338
|
def ollama_probe(model_id, input, max_tokens)
|
|
311
339
|
base_url = @api_endpoint.to_s.delete_suffix("/")
|
|
312
340
|
conn = Faraday.new(url: base_url) do |f|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module CompletionKit
|
|
2
|
+
module Onboarding
|
|
3
|
+
Step = Data.define(:key, :title, :description, :path_name, :done?)
|
|
4
|
+
|
|
5
|
+
class Checklist
|
|
6
|
+
STEP_DEFS = [
|
|
7
|
+
{
|
|
8
|
+
key: :credential,
|
|
9
|
+
title: "Connect a provider",
|
|
10
|
+
description: "Add an API key for OpenAI, Anthropic, or another model provider so you can run prompts and score outputs.",
|
|
11
|
+
path_name: :new_provider_credential_path,
|
|
12
|
+
done: -> { CompletionKit::ProviderCredential.exists? }
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
key: :dataset,
|
|
16
|
+
title: "Upload a dataset",
|
|
17
|
+
description: "Drop in a CSV of test inputs. Column headers become the {{variables}} your prompts get evaluated against.",
|
|
18
|
+
path_name: :new_dataset_path,
|
|
19
|
+
done: -> { CompletionKit::Dataset.exists? }
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: :prompt,
|
|
23
|
+
title: "Write your first prompt",
|
|
24
|
+
description: "Create a versioned prompt template with {{variable}} placeholders. Publishing freezes it; editing makes a new version.",
|
|
25
|
+
path_name: :new_prompt_path,
|
|
26
|
+
done: -> { CompletionKit::Prompt.exists? }
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: :run,
|
|
30
|
+
title: "Run it",
|
|
31
|
+
description: "Kick off a run against the dataset and watch the responses come in, each scored by the LLM judge against your metrics.",
|
|
32
|
+
path_name: :runs_path,
|
|
33
|
+
done: -> { CompletionKit::Run.exists? }
|
|
34
|
+
}
|
|
35
|
+
].freeze
|
|
36
|
+
|
|
37
|
+
def steps
|
|
38
|
+
@steps ||= STEP_DEFS.map do |d|
|
|
39
|
+
Step.new(
|
|
40
|
+
key: d[:key],
|
|
41
|
+
title: d[:title],
|
|
42
|
+
description: d[:description],
|
|
43
|
+
path_name: d[:path_name],
|
|
44
|
+
done?: d[:done].call
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def complete?
|
|
50
|
+
steps.all?(&:done?)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Whether the "Load sample data" button should show — only while neither
|
|
54
|
+
# the dataset nor the prompt step is done (SampleData.install! no-ops
|
|
55
|
+
# otherwise, so the button would do nothing).
|
|
56
|
+
def sample_loadable?
|
|
57
|
+
steps.none? { |s| %i[dataset prompt].include?(s.key) && s.done? }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def progress
|
|
61
|
+
done = steps.count(&:done?)
|
|
62
|
+
{ done: done, total: steps.size, percent: ((done.to_f / steps.size) * 100).round }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module CompletionKit
|
|
2
|
+
module Onboarding
|
|
3
|
+
# Opt-in starter data for the onboarding page: one dataset + one prompt so a
|
|
4
|
+
# brand-new install has something to poke at. Idempotent — a no-op once the
|
|
5
|
+
# workspace already has any prompt or dataset. Deliberately does NOT create a
|
|
6
|
+
# provider credential (needs a real API key) or a run (user-initiated).
|
|
7
|
+
module SampleData
|
|
8
|
+
SAMPLE_CSV = <<~CSV.freeze
|
|
9
|
+
ticket
|
|
10
|
+
"My order #4827 arrived with a dented panel. I emailed photos 11 days ago and heard nothing. Today I was told the return window 'closed'. I paid $749. I want a refund or replacement, not store credit."
|
|
11
|
+
"Tracking says delivered to my porch Tuesday 3:47pm. I was home all day, nothing arrived, neighbours' cameras show no van. Order #5102 — a $315 mixer, wedding gift, wedding is Saturday. Can someone look today?"
|
|
12
|
+
"WELCOME20 says 'invalid' at checkout but the promo email says it's good through May 31. Same email I'm signed in with. Tried Chrome and Safari. Cart is $186 waiting on you."
|
|
13
|
+
CSV
|
|
14
|
+
|
|
15
|
+
SAMPLE_PROMPT = {
|
|
16
|
+
name: "Sample: Support reply",
|
|
17
|
+
description: "A starter prompt — drafts a warm, professional reply to a customer support ticket. Edit it or delete it; it's just here to get you going.",
|
|
18
|
+
template: "You are a senior customer-support specialist. Write a warm, professional reply to this ticket. Acknowledge the customer's situation, be specific about next steps, and don't be defensive.\n\nTicket:\n{{ticket}}",
|
|
19
|
+
llm_model: "gpt-4o-mini"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
def install!
|
|
25
|
+
return if CompletionKit::Prompt.exists? || CompletionKit::Dataset.exists?
|
|
26
|
+
|
|
27
|
+
CompletionKit::Dataset.create!(name: "Sample: Customer tickets", csv_data: SAMPLE_CSV)
|
|
28
|
+
CompletionKit::Prompt.create!(
|
|
29
|
+
name: SAMPLE_PROMPT[:name],
|
|
30
|
+
description: SAMPLE_PROMPT[:description],
|
|
31
|
+
template: SAMPLE_PROMPT[:template],
|
|
32
|
+
llm_model: SAMPLE_PROMPT[:llm_model]
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<div class="ck-launch <%= "ck-launch--ready" if @checklist.complete? %>">
|
|
2
|
+
<div class="ck-launch__field" aria-hidden="true"></div>
|
|
3
|
+
|
|
4
|
+
<header class="ck-launch__header">
|
|
5
|
+
<p class="ck-launch__overline">Welcome to CompletionKit</p>
|
|
6
|
+
<h1 class="ck-launch__org">Your prompts need tests too</h1>
|
|
7
|
+
<p class="ck-launch__handle">Four steps from a fresh install to a scored test run.</p>
|
|
8
|
+
</header>
|
|
9
|
+
|
|
10
|
+
<% if @checklist.complete? %>
|
|
11
|
+
<section class="ck-launch__ready-panel">
|
|
12
|
+
<p class="ck-launch__ready-flag">
|
|
13
|
+
<span class="ck-launch__ready-dot" aria-hidden="true"></span>
|
|
14
|
+
You're all set up
|
|
15
|
+
</p>
|
|
16
|
+
<p class="ck-launch__ready-copy">Jump in wherever you like.</p>
|
|
17
|
+
<div class="ck-launch__quicklinks">
|
|
18
|
+
<%= link_to "Prompts", prompts_path, class: "ck-launch__quicklink" %>
|
|
19
|
+
<%= link_to "Datasets", datasets_path, class: "ck-launch__quicklink" %>
|
|
20
|
+
<%= link_to "Metrics", metrics_path, class: "ck-launch__quicklink" %>
|
|
21
|
+
<%= link_to "Runs", runs_path, class: "ck-launch__quicklink" %>
|
|
22
|
+
</div>
|
|
23
|
+
</section>
|
|
24
|
+
<% else %>
|
|
25
|
+
<% next_index = @checklist.steps.index { |s| !s.done? } %>
|
|
26
|
+
<% progress = @checklist.progress %>
|
|
27
|
+
<section class="ck-launch__panel">
|
|
28
|
+
<div class="ck-launch__progress">
|
|
29
|
+
<div class="ck-launch__progress-head">
|
|
30
|
+
<h2 class="ck-launch__progress-title">Finish setting up</h2>
|
|
31
|
+
<p class="ck-launch__progress-count"><%= progress[:done] %> of <%= progress[:total] %> done</p>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="ck-launch__progress-bar" role="img" aria-label="<%= progress[:done] %> of <%= progress[:total] %> setup steps complete">
|
|
34
|
+
<span class="ck-launch__progress-fill" style="width: <%= progress[:percent] %>%"></span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<ol class="ck-launch__steps">
|
|
39
|
+
<% @checklist.steps.each_with_index do |step, i| %>
|
|
40
|
+
<%
|
|
41
|
+
state = if step.done?
|
|
42
|
+
"done"
|
|
43
|
+
elsif i == next_index
|
|
44
|
+
"next"
|
|
45
|
+
else
|
|
46
|
+
"pending"
|
|
47
|
+
end
|
|
48
|
+
step_path = public_send(step.path_name)
|
|
49
|
+
%>
|
|
50
|
+
<li class="ck-launch__step ck-launch__step--<%= state %>" style="--i: <%= i %>">
|
|
51
|
+
<span class="ck-launch__step-num" aria-hidden="true"><%= format("%02d", i + 1) %><% if step.done? %><span class="ck-launch__step-tick">✓</span><% end %></span>
|
|
52
|
+
<div class="ck-launch__step-body">
|
|
53
|
+
<div class="ck-launch__step-head">
|
|
54
|
+
<h3 class="ck-launch__step-title">
|
|
55
|
+
<% if step.done? %>
|
|
56
|
+
<%= step.title %>
|
|
57
|
+
<% else %>
|
|
58
|
+
<%= link_to step.title, step_path %>
|
|
59
|
+
<% end %>
|
|
60
|
+
</h3>
|
|
61
|
+
<% if state == "next" %><span class="ck-launch__step-tag">Next up</span><% end %>
|
|
62
|
+
</div>
|
|
63
|
+
<p class="ck-launch__step-desc"><%= step.description %></p>
|
|
64
|
+
<% if state == "next" %>
|
|
65
|
+
<%= link_to step_path, class: "ck-launch__step-cta" do %>Start <span aria-hidden="true">→</span><% end %>
|
|
66
|
+
<% end %>
|
|
67
|
+
</div>
|
|
68
|
+
</li>
|
|
69
|
+
<% end %>
|
|
70
|
+
</ol>
|
|
71
|
+
|
|
72
|
+
<% if @checklist.sample_loadable? %>
|
|
73
|
+
<div class="ck-launch__sample">
|
|
74
|
+
<p class="ck-launch__sample-copy">Just exploring? Drop in a sample dataset and prompt to poke around — they're labelled <code>Sample:</code>, and you can edit or delete them whenever.</p>
|
|
75
|
+
<%= button_to onboarding_sample_data_path, method: :post, class: "ck-launch__sample-cta", form: { style: "display:inline" } do %>
|
|
76
|
+
Load sample data <span aria-hidden="true">→</span>
|
|
77
|
+
<% end %>
|
|
78
|
+
</div>
|
|
79
|
+
<% end %>
|
|
80
|
+
|
|
81
|
+
<div class="ck-launch__panel-footer">
|
|
82
|
+
<%= button_to dismiss_onboarding_path, method: :post, class: "ck-launch__dismiss", form: { style: "display:inline" } do %>
|
|
83
|
+
Skip setup — go to the app <span aria-hidden="true">→</span>
|
|
84
|
+
<% end %>
|
|
85
|
+
<p class="ck-launch__dismiss-hint">You can come back to this from <strong>Settings → Getting started</strong> any time.</p>
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
<% end %>
|
|
89
|
+
</div>
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<% judged_run = @runs.select { |r| r.prompt_id == @prompt.id && r.status == "completed" }.find { |r| r.responses.joins(:reviews).exists? } %>
|
|
38
38
|
<% if judged_run %>
|
|
39
39
|
<%= button_to suggest_run_path(judged_run), method: :post, class: ck_button_classes(:light, variant: :outline) + " ck-button--sm", form_class: "inline-block" do %>
|
|
40
|
-
<%= heroicon_tag "sparkles", variant: :outline,
|
|
40
|
+
<%= heroicon_tag "sparkles", variant: :outline, class: "ck-magic-icon", "aria-hidden": "true" %>
|
|
41
41
|
Suggest improvements
|
|
42
42
|
<% end %>
|
|
43
43
|
<% else %>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
Gen<span class="ck-info-toggle" tabindex="0">?</span><span class="ck-info-popup ck-info-popup--right">Generation models produce the responses your prompts ask for. Pick one when creating a prompt.</span>
|
|
37
37
|
</th>
|
|
38
38
|
<th scope="col" class="ck-model-table__cap">
|
|
39
|
-
Judge<span class="ck-info-toggle" tabindex="0">?</span><span class="ck-info-popup ck-info-popup--right">Judge models score generated responses against your metrics. Pick one when configuring a run
|
|
39
|
+
Judge<span class="ck-info-toggle" tabindex="0">?</span><span class="ck-info-popup ck-info-popup--right">Judge models score generated responses against your metrics. Pick one when configuring a run. A <strong>?</strong> means we haven't confirmed this model works as a judge — it's still selectable, and a successful run promotes it to ✓.</span>
|
|
40
40
|
</th>
|
|
41
41
|
</tr>
|
|
42
42
|
</thead>
|
|
@@ -54,8 +54,10 @@
|
|
|
54
54
|
<td class="ck-model-table__cap">
|
|
55
55
|
<% if m.supports_judging %>
|
|
56
56
|
<span class="ck-model-table__tick" aria-label="Supports judging">✓</span>
|
|
57
|
+
<% elsif m.supports_judging.nil? %>
|
|
58
|
+
<span class="ck-model-table__unknown" aria-label="Untested as judge" title="Untested as a judge — selectable; a successful run confirms it">?</span>
|
|
57
59
|
<% else %>
|
|
58
|
-
<span class="ck-model-table__dash" aria-label="
|
|
60
|
+
<span class="ck-model-table__dash" aria-label="Not usable as judge">—</span>
|
|
59
61
|
<% end %>
|
|
60
62
|
</td>
|
|
61
63
|
</tr>
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
<%= link_to "View suggestion", suggestion_path(latest_suggestion, from: "run"), class: ck_button_classes(:light, variant: :outline) + " ck-button--sm" %>
|
|
68
68
|
<% elsif @run.status == "completed" && @run.responses.joins(:reviews).exists? %>
|
|
69
69
|
<%= button_to suggest_run_path(@run), method: :post, class: ck_button_classes(:light, variant: :outline) + " ck-button--sm", form_class: "inline-block" do %>
|
|
70
|
-
<%= heroicon_tag "sparkles", variant: :outline,
|
|
70
|
+
<%= heroicon_tag "sparkles", variant: :outline, class: "ck-magic-icon", "aria-hidden": "true" %>
|
|
71
71
|
Suggest improvements
|
|
72
72
|
<% end %>
|
|
73
73
|
<% end %>
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<%= link_to "Metrics", metrics_path, class: request.path.start_with?(metrics_path) || request.path.start_with?(metric_groups_path) ? ck_button_classes(:dark) : ck_button_classes(:light, variant: :outline) %>
|
|
24
24
|
<%= link_to "Datasets", datasets_path, class: active.(datasets_path) %>
|
|
25
25
|
<%= link_to "Runs", runs_path, class: active.(runs_path) %>
|
|
26
|
-
<% settings_active = request.path.start_with?(provider_credentials_path) || request.path.start_with?(tags_path) %>
|
|
26
|
+
<% settings_active = request.path.start_with?(provider_credentials_path) || request.path.start_with?(tags_path) || request.path.start_with?(onboarding_path) %>
|
|
27
27
|
<details class="ck-settings-menu">
|
|
28
28
|
<summary class="<%= settings_active ? ck_button_classes(:dark) : ck_button_classes(:light, variant: :outline) %> ck-settings-menu__trigger" aria-label="Settings">
|
|
29
29
|
Settings
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
<div class="ck-settings-menu__panel" role="menu">
|
|
32
32
|
<%= link_to "Providers", provider_credentials_path, class: "ck-settings-menu__item" %>
|
|
33
33
|
<%= link_to "Tags", tags_path, class: "ck-settings-menu__item" %>
|
|
34
|
+
<%= link_to "Getting started", onboarding_path(reset: 1), class: "ck-settings-menu__item" %>
|
|
34
35
|
</div>
|
|
35
36
|
</details>
|
|
36
37
|
<%= link_to "API", api_reference_path, class: active.(api_reference_path) %>
|
data/config/routes.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
CompletionKit::Engine.routes.draw do
|
|
2
|
-
root to: "
|
|
2
|
+
root to: "onboarding#show"
|
|
3
|
+
|
|
4
|
+
get "onboarding", to: "onboarding#show", as: :onboarding
|
|
5
|
+
post "onboarding/dismiss", to: "onboarding#dismiss", as: :dismiss_onboarding
|
|
6
|
+
post "onboarding/sample-data", to: "onboarding#sample_data", as: :onboarding_sample_data
|
|
3
7
|
|
|
4
8
|
resources :prompts do
|
|
5
9
|
member do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: completion-kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Damien Bastin
|
|
@@ -245,6 +245,7 @@ files:
|
|
|
245
245
|
- app/controllers/completion_kit/mcp_controller.rb
|
|
246
246
|
- app/controllers/completion_kit/metric_groups_controller.rb
|
|
247
247
|
- app/controllers/completion_kit/metrics_controller.rb
|
|
248
|
+
- app/controllers/completion_kit/onboarding_controller.rb
|
|
248
249
|
- app/controllers/completion_kit/prompts_controller.rb
|
|
249
250
|
- app/controllers/completion_kit/provider_credentials_controller.rb
|
|
250
251
|
- app/controllers/completion_kit/responses_controller.rb
|
|
@@ -292,6 +293,8 @@ files:
|
|
|
292
293
|
- app/services/completion_kit/mcp_tools/tags.rb
|
|
293
294
|
- app/services/completion_kit/model_discovery_service.rb
|
|
294
295
|
- app/services/completion_kit/ollama_client.rb
|
|
296
|
+
- app/services/completion_kit/onboarding/checklist.rb
|
|
297
|
+
- app/services/completion_kit/onboarding/sample_data.rb
|
|
295
298
|
- app/services/completion_kit/open_ai_client.rb
|
|
296
299
|
- app/services/completion_kit/open_router_client.rb
|
|
297
300
|
- app/services/completion_kit/prompt_improvement_service.rb
|
|
@@ -314,6 +317,7 @@ files:
|
|
|
314
317
|
- app/views/completion_kit/metrics/index.html.erb
|
|
315
318
|
- app/views/completion_kit/metrics/new.html.erb
|
|
316
319
|
- app/views/completion_kit/metrics/show.html.erb
|
|
320
|
+
- app/views/completion_kit/onboarding/show.html.erb
|
|
317
321
|
- app/views/completion_kit/prompts/_form.html.erb
|
|
318
322
|
- app/views/completion_kit/prompts/edit.html.erb
|
|
319
323
|
- app/views/completion_kit/prompts/index.html.erb
|