Skip to content

Commit d209d14

Browse files
authored
Merge pull request #3268 from codecrafters-io/pk-branch-7
Add autofix result integration
2 parents 1587c06 + 774976d commit d209d14

File tree

12 files changed

+525
-149
lines changed

12 files changed

+525
-149
lines changed

app/components/course-page/test-results-bar/autofix-section/autofix-result.hbs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,6 @@
1818
<span class="font-mono text-xs text-white">Hints generated</span>
1919
{{/if}}
2020
</div>
21-
{{#unless (eq @autofixRequest.status "in_progress")}}
22-
<div
23-
class="text-gray-400 underline text-xs px-3 py-2 inline-flex hover:text-gray-300"
24-
{{on "click" (fn (mut this.shouldShowFullLog) (not this.shouldShowFullLog))}}
25-
role="button"
26-
>
27-
{{#if this.shouldShowFullLog}}
28-
Show summary
29-
{{else}}
30-
Show Full Log
31-
{{/if}}
32-
</div>
33-
{{/unless}}
3421
</div>
3522

3623
{{#if (eq @autofixRequest.status "in_progress")}}
@@ -47,15 +34,11 @@
4734
</div>
4835
</div>
4936
{{else}}
50-
{{#if this.shouldShowFullLog}}
51-
<div class="bg-gray-800 border border-gray-700 rounded-sm grow overflow-y-auto">
52-
<div class="p-3">
53-
<pre class="font-mono text-xs text-white whitespace-pre-wrap"><code>{{ansi-to-html @autofixRequest.logs}}</code></pre>
54-
</div>
55-
</div>
56-
{{else if (eq @autofixRequest.status "success")}}
37+
{{#if (eq @autofixRequest.status "success")}}
5738
<div class="grow overflow-y-auto">
5839
<div class="prose prose-sm dark:prose-invert has-prism-highlighting" {{highlight-code-blocks @autofixRequest.explanationMarkdown}}>
40+
<h3>Hint: {{@autofixRequest.summary}}</h3>
41+
5942
{{markdown-to-html @autofixRequest.explanationMarkdown}}
6043
</div>
6144

@@ -65,13 +48,6 @@
6548
Share Feedback
6649
</TertiaryButton>
6750
</FeedbackButton>
68-
69-
<a
70-
href="https://docs.codecrafters.io/experimental/ai-hints"
71-
target="_blank"
72-
rel="noopener noreferrer"
73-
class="text-sm text-gray-400 underline"
74-
>Learn more</a>.
7551
</div>
7652

7753
<BlurredOverlay @isBlurred={{this.diffIsBlurred}} @overlayClass="bg-gray-900/20">
Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,3 @@
11
<div class="pb-3 flex flex-col" ...attributes data-test-autofix-section>
2-
{{#if this.lastSubmission}}
3-
{{#if this.lastAutofixRequest}}
4-
<CoursePage::TestResultsBar::AutofixSection::AutofixResult @autofixRequest={{this.lastAutofixRequest}} class="overflow-y-auto grow" />
5-
{{else}}
6-
<AlertWithIcon class="mb-3">
7-
<p>
8-
We're testing out a new feature called "AI Hints". This is currently only available on stage 2.
9-
<a href="https://docs.codecrafters.io/experimental/ai-hints" target="_blank" rel="noopener noreferrer">Learn more</a>.
10-
</p>
11-
</AlertWithIcon>
12-
13-
<CoursePage::TestResultsBar::AutofixSection::CreateAutofixPrompt @submission={{this.lastSubmission}} />
14-
{{/if}}
15-
{{else}}
16-
{{! TODO: Review copy here }}
17-
<div class="py-16 flex items-center justify-center grow">
18-
<div class="text-gray-500 text-sm">
19-
{{#if (eq @activeStep.type "CourseStageStep")}}
20-
{{#if (eq @activeStep @currentStep)}}
21-
After you've run tests for this stage once, you'll be able to use the "AI Hints" feature here to help automatically fix any issues.
22-
{{else}}
23-
After you've run tests for the
24-
<LinkTo
25-
class="font-semibold text-gray-400 underline hover:text-gray-300"
26-
@route={{@activeStep.routeParams.route}}
27-
@models={{@activeStep.routeParams.models}}
28-
>{{@activeStep.title}}</LinkTo>
29-
stage, you'll be able to use the "AI Hints" feature here to help automatically fix any issues.
30-
{{/if}}
31-
{{else}}
32-
After you've run tests on your code, you'll be able to use the "AI Hints" feature here to help automatically fix any issues.
33-
{{/if}}
34-
</div>
35-
</div>
36-
{{/if}}
2+
<CoursePage::TestResultsBar::AutofixSection::AutofixResult @autofixRequest={{@autofixRequest}} class="overflow-y-auto grow" />
373
</div>

app/components/course-page/test-results-bar/autofix-section/index.ts

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,15 @@
1-
import type Store from '@ember-data/store';
2-
import { service } from '@ember/service';
31
import Component from '@glimmer/component';
4-
import { StepDefinition } from 'codecrafters-frontend/utils/course-page-step-list';
5-
import type CourseStageStep from 'codecrafters-frontend/utils/course-page-step-list/course-stage-step';
62
import type AutofixRequestModel from 'codecrafters-frontend/models/autofix-request';
7-
import type RepositoryModel from 'codecrafters-frontend/models/repository';
8-
import { tracked } from '@glimmer/tracking';
9-
import fieldComparator from 'codecrafters-frontend/utils/field-comparator';
103

114
interface Signature {
125
Element: HTMLDivElement;
136

147
Args: {
15-
activeStep: StepDefinition;
16-
currentStep: StepDefinition;
17-
repository: RepositoryModel;
8+
autofixRequest: AutofixRequestModel;
189
};
1910
}
2011

21-
export default class AutofixSection extends Component<Signature> {
22-
@service declare store: Store;
23-
@tracked autofixCreationError: string | null = null;
24-
25-
get activeCourseStage() {
26-
if (this.args.activeStep.type === 'CourseStageStep') {
27-
return this.activeStepAsCourseStageStep.courseStage;
28-
} else {
29-
return null;
30-
}
31-
}
32-
33-
get activeStepAsCourseStageStep() {
34-
return this.args.activeStep as CourseStageStep;
35-
}
36-
37-
get lastAutofixRequest(): AutofixRequestModel | null {
38-
if (!this.lastSubmission) {
39-
return null;
40-
}
41-
42-
return (
43-
this.lastSubmission.autofixRequests
44-
.filter((item) => !item.isNew)
45-
.filter((item) => !item.creatorTypeIsSystem)
46-
.sort(fieldComparator('createdAt'))
47-
.at(-1) || null
48-
);
49-
}
50-
51-
get lastSubmission() {
52-
if (!this.activeCourseStage) {
53-
return null;
54-
}
55-
56-
if (this.args.repository.lastSubmission.courseStage === this.activeCourseStage) {
57-
return this.args.repository.lastSubmission;
58-
} else {
59-
return null;
60-
}
61-
}
62-
}
12+
export default class AutofixSection extends Component<Signature> {}
6313

6414
declare module '@glint/environment-ember-loose/registry' {
6515
export default interface Registry {

app/components/course-page/test-results-bar/index.hbs

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,45 @@
1818
{{/if}}
1919

2020
<div
21-
class="relative w-full flex flex-col grow overflow-auto {{unless this.isResizing 'transition-[height] duration-200 ease-out'}}"
21+
class="relative w-full flex flex-col grow {{unless this.isResizing 'transition-[height] duration-200 ease-out'}}"
2222
style={{this.expandedContainerStyle}}
2323
data-test-contents
2424
>
2525
{{#if this.isExpanded}}
26-
<div class="flex flex-col grow border-b border-gray-800 px-6">
27-
<CoursePage::TestResultsBar::TopSection
28-
@activeTabSlug={{this.activeTabSlug}}
29-
@onActiveTabSlugChange={{fn (mut this.activeTabSlug)}}
30-
@availableTabSlugs={{this.availableTabSlugs}}
31-
class="mt-3 mb-3"
32-
/>
26+
{{! min-h-0 ensures div doesn't outgrow container !}}
27+
<div class="flex gap-6 grow border-b border-gray-800 px-6 min-h-0">
28+
<CoursePage::TestResultsBar::Pane
29+
@activeTabSlug={{this.activeTabSlugForLeftPane}}
30+
@onActiveTabSlugChange={{fn (mut this.activeTabSlugForLeftPane)}}
31+
@availableTabSlugs={{this.availableTabSlugsForLeftPane}}
32+
class="transition-width duration-200 ease-out {{if this.shouldShowRightPane 'w-1/2' 'w-full'}} grow"
33+
>
34+
{{#if (eq this.activeTabSlugForLeftPane "logs")}}
35+
<CoursePage::TestResultsBar::LogsSection
36+
@activeStep={{@activeStep}}
37+
@currentStep={{@currentStep}}
38+
@repository={{@repository}}
39+
class="grow min-h-0"
40+
/>
41+
{{else if (eq this.activeTabSlugForLeftPane "autofix")}}
42+
{{! Extra if convinces glint that autofixRequest is not null }}
43+
{{#if this.autofixRequestForActiveStep}}
44+
<CoursePage::TestResultsBar::AutofixSection @autofixRequest={{this.autofixRequestForActiveStep}} class="grow min-h-0" />
45+
{{/if}}
46+
{{/if}}
47+
</CoursePage::TestResultsBar::Pane>
3348

34-
{{#if (eq this.activeTabSlug "logs")}}
35-
<CoursePage::TestResultsBar::LogsSection
36-
@activeStep={{@activeStep}}
37-
@currentStep={{@currentStep}}
38-
@repository={{@repository}}
39-
class="grow overflow-y-auto"
40-
/>
41-
{{else if (eq this.activeTabSlug "autofix")}}
42-
<CoursePage::TestResultsBar::AutofixSection
43-
@activeStep={{@activeStep}}
44-
@currentStep={{@currentStep}}
45-
@repository={{@repository}}
46-
class="grow overflow-y-auto"
47-
/>
48-
{{/if}}
49+
<CoursePage::TestResultsBar::Pane
50+
@activeTabSlug={{this.activeTabSlugForRightPane}}
51+
@onActiveTabSlugChange={{fn (mut this.activeTabSlugForRightPane)}}
52+
@availableTabSlugs={{this.availableTabSlugsForRightPane}}
53+
class="transition-[width,opacity] duration-200 ease-out {{if this.shouldShowRightPane 'w-1/2 opacity-100' 'w-[0] opacity-0'}} grow"
54+
>
55+
{{! Extra if convinces glint that autofixRequest is not null }}
56+
{{#if this.autofixRequestForActiveStep}}
57+
<CoursePage::TestResultsBar::AutofixSection @autofixRequest={{this.autofixRequestForActiveStep}} class="grow min-h-0" />
58+
{{/if}}
59+
</CoursePage::TestResultsBar::Pane>
4960
</div>
5061

5162
<div

app/components/course-page/test-results-bar/index.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import type AuthenticatorService from 'codecrafters-frontend/services/authentica
99
import type CoursePageStateService from 'codecrafters-frontend/services/course-page-state';
1010
import type CourseStageStep from 'codecrafters-frontend/utils/course-page-step-list/course-stage-step';
1111
import type RepositoryModel from 'codecrafters-frontend/models/repository';
12+
import fade from 'ember-animated/transitions/fade';
13+
import type AutofixRequestModel from 'codecrafters-frontend/models/autofix-request';
1214

1315
interface Signature {
1416
Element: HTMLDivElement;
@@ -20,32 +22,71 @@ interface Signature {
2022
};
2123
}
2224

25+
interface ScreenService {
26+
width: number;
27+
}
28+
2329
export default class TestResultsBar extends Component<Signature> {
24-
@service declare coursePageState: CoursePageStateService;
30+
transition = fade;
31+
2532
@service declare authenticator: AuthenticatorService;
26-
@tracked activeTabSlug = 'logs'; // 'logs' | 'autofix'
33+
@service declare coursePageState: CoursePageStateService;
34+
@service declare screen: ScreenService;
35+
36+
@tracked activeTabSlugForLeftPane = 'logs'; // 'logs' | 'autofix'
37+
@tracked activeTabSlugForRightPane = 'autofix';
2738
@tracked bottomSectionElement: HTMLDivElement | null = null;
2839
@tracked expandedContainerHeight = '75vh';
2940
@tracked isResizing = false;
3041

42+
get autofixRequestForActiveStep(): AutofixRequestModel | null {
43+
if (this.args.activeStep.type !== 'CourseStageStep') {
44+
return null;
45+
}
46+
47+
const courseStageStep = this.args.activeStep as CourseStageStep;
48+
49+
return (
50+
(this.args.repository.lastSubmission?.autofixRequests || [])
51+
.filter((request) => !request.creatorTypeIsStaff)
52+
.filter((request) => request.submission === this.args.repository.lastSubmission)
53+
.filter((request) => request.submission.courseStage === courseStageStep.courseStage)
54+
.at(-1) || null
55+
);
56+
}
57+
3158
get availableTabSlugs() {
3259
if (this.args.activeStep.type === 'CourseStageStep') {
3360
const courseStageStep = this.args.activeStep as CourseStageStep;
3461

3562
if (courseStageStep.courseStage.isFirst) {
3663
return ['logs'];
3764
} else {
38-
if (this.authenticator.currentUser?.isStaff) {
65+
if (this.authenticator.currentUser?.isStaff && this.autofixRequestForActiveStep) {
3966
return ['logs', 'autofix'];
4067
} else {
4168
return ['logs'];
4269
}
70+
71+
// return ['logs'];
4372
}
4473
} else {
4574
return ['logs'];
4675
}
4776
}
4877

78+
get availableTabSlugsForLeftPane() {
79+
return this.availableTabSlugs.filter((slug) => !this.availableTabSlugsForRightPane.includes(slug));
80+
}
81+
82+
get availableTabSlugsForRightPane() {
83+
if (this.screen.width > 1024 && this.availableTabSlugs.length > 1) {
84+
return ['autofix'];
85+
} else {
86+
return [];
87+
}
88+
}
89+
4990
get expandedContainerStyle() {
5091
if (this.isExpanded) {
5192
return htmlSafe(`height: ${this.expandedContainerHeight}`);
@@ -62,6 +103,10 @@ export default class TestResultsBar extends Component<Signature> {
62103
return this.coursePageState.testResultsBarIsExpanded;
63104
}
64105

106+
get shouldShowRightPane() {
107+
return this.availableTabSlugsForRightPane.length > 0;
108+
}
109+
65110
@action
66111
handleCollapseButtonClick() {
67112
this.coursePageState.testResultsBarIsExpanded = false;

app/components/course-page/test-results-bar/top-section.hbs renamed to app/components/course-page/test-results-bar/pane.hbs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
<div class="flex items-center justify-between" ...attributes>
2-
<ul class="flex items-center gap-2 overflow-x-auto">
1+
<div class="flex flex-col" ...attributes>
2+
<ul class="flex items-center gap-2 overflow-x-auto mt-3 mb-3 shrink-0">
33
{{#each this.tabs as |tab|}}
44
<TabHeader
55
@size="small"
@@ -10,4 +10,6 @@
1010
/>
1111
{{/each}}
1212
</ul>
13+
14+
{{yield}}
1315
</div>

app/components/course-page/test-results-bar/top-section.ts renamed to app/components/course-page/test-results-bar/pane.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ interface Signature {
99
availableTabSlugs: string[];
1010
onActiveTabSlugChange: (slug: string) => void;
1111
};
12+
13+
Blocks: {
14+
default: [];
15+
};
1216
}
1317

14-
export default class TopSection extends Component<Signature> {
18+
export default class Pane extends Component<Signature> {
1519
get tabs() {
1620
const allTabs = [
1721
{
@@ -37,6 +41,6 @@ export default class TopSection extends Component<Signature> {
3741

3842
declare module '@glint/environment-ember-loose/registry' {
3943
export default interface Registry {
40-
'CoursePage::TestResultsBar::TopSection': typeof TopSection;
44+
'CoursePage::TestResultsBar::Pane': typeof Pane;
4145
}
4246
}

app/models/autofix-request.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ export default class AutofixRequestModel extends Model {
66
@belongsTo('submission', { async: false, inverse: 'autofixRequests' }) declare submission: SubmissionModel;
77
@belongsTo('repository', { async: false, inverse: 'autofixRequests' }) declare repository: RepositoryModel;
88

9-
@attr('string') declare creatorType: 'user' | 'system';
9+
@attr('string') declare creatorType: 'user' | 'system' | 'staff';
1010
@attr() declare changedFiles: { diff: string; filename: string }[]; // free-form JSON
1111
@attr('date') declare createdAt: Date;
1212
@attr('string') declare explanationMarkdown: string;
1313
@attr('string') declare logstreamId: string; // For streaming logs when status is in_progress
1414
@attr('string') declare logsBase64: string; // Base64-encoded logs
1515
@attr('number') declare resultDelayInMilliseconds: number | null;
1616
@attr('string') declare status: string; // 'in_progress' | 'success' | 'failure' | 'error'
17+
@attr('string') declare summary: string;
1718

18-
get creatorTypeIsSystem() {
19-
return this.creatorType === 'system';
19+
get creatorTypeIsStaff() {
20+
return this.creatorType === 'staff';
2021
}
2122

2223
get logs(): string {

0 commit comments

Comments
 (0)