Skip to content

Commit 2280282

Browse files
committed
feat(logstream-section): simplify toolCall text and improve animations
Refactor toolCall text rendering to a single string instead of textComponents array for clearer and more concise display. Wrap each toolCall in an AnimatedContainer and apply distinct listTransition for improved animation handling of tool call items. Update text getters to directly return the full status string, improving maintainability and readability. Remove redundant placeholder toolCalls when no events exist to streamline UI logic. These changes enhance UX by providing smoother transitions and clearer status messages.
1 parent 79d47c2 commit 2280282

File tree

2 files changed

+77
-32
lines changed

2 files changed

+77
-32
lines changed

app/components/course-page/test-results-bar/autofix-request-card/logstream-section.hbs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
<div class="overflow-y-auto h-32" {{did-insert this.handleDidInsertEventsContainer}} ...attributes>
22
<AnimatedContainer>
3-
{{#animated-each this.toolCalls key="tool_call_id" use=this.transition duration=300 as |toolCall|}}
4-
<div>
5-
{{#animated-if (eq toolCall.status "in_progress") use=this.transition duration=300}}
6-
<div class="text-xs text-gray-300">
7-
<TextShimmer @text={{concat toolCall.textComponents "1"}} />
8-
</div>
9-
{{else}}
10-
<div class="text-xs text-gray-500">
11-
{{get toolCall.textComponents "0"}}
12-
{{get toolCall.textComponents "1"}}
13-
</div>
14-
{{/animated-if}}
3+
{{#animated-each this.toolCalls key="tool_call_id" use=this.listTransition duration=300 as |toolCall|}}
4+
<div class="py-0.5">
5+
<AnimatedContainer>
6+
{{#animated-if (eq toolCall.status "in_progress") use=this.transition duration=300}}
7+
<div class="text-xs text-gray-300">
8+
<TextShimmer @text={{toolCall.text}} />
9+
</div>
10+
{{else}}
11+
<div class="text-xs text-gray-500">
12+
{{toolCall.text}}
13+
</div>
14+
{{/animated-if}}
15+
</AnimatedContainer>
1516
</div>
1617
{{/animated-each}}
1718
</AnimatedContainer>

app/components/course-page/test-results-bar/autofix-request-card/logstream-section.ts

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import Component from '@glimmer/component';
22
import Logstream from 'codecrafters-frontend/utils/logstream';
3+
import fade from 'ember-animated/transitions/fade';
4+
import move from 'ember-animated/motions/move';
35
import type ActionCableConsumerService from 'codecrafters-frontend/services/action-cable-consumer';
46
import type AutofixRequestModel from 'codecrafters-frontend/models/autofix-request';
57
import type Store from '@ember-data/store';
68
import { action } from '@ember/object';
9+
import { fadeIn, fadeOut } from 'ember-animated/motions/opacity';
10+
import { next } from '@ember/runloop';
711
import { service } from '@ember/service';
812
import { tracked } from '@glimmer/tracking';
9-
import { next } from '@ember/runloop';
10-
import fade from 'ember-animated/transitions/fade';
1113

1214
interface Signature {
1315
Element: HTMLDivElement;
@@ -46,55 +48,66 @@ class ToolCall {
4648
this.status = 'in_progress';
4749
}
4850

49-
get textComponents(): [string, string] {
51+
get text(): string {
5052
switch (this.tool_name) {
5153
case 'read':
5254
if (this.status === 'in_progress') {
53-
return ['Reading', 'abcd.rb'];
55+
return `Reading ${this.tool_arguments['path']!}`;
5456
} else {
55-
return ['Read', 'abcd.rb'];
57+
return `Read ${this.tool_arguments['path']!}`;
5658
}
5759

5860
case 'edit':
5961
if (this.status === 'in_progress') {
60-
return ['Editing', 'abcd.rb'];
62+
return `Editing ${this.tool_arguments['path']!}`;
63+
} else {
64+
return `Edited ${this.tool_arguments['path']!}`;
65+
}
66+
67+
case 'write':
68+
if (this.status === 'in_progress') {
69+
return `Creating ${this.tool_arguments['path']!}`;
6170
} else {
62-
return ['Edited', 'abcd.rb'];
71+
return `Created ${this.tool_arguments['path']!}`;
6372
}
6473

6574
case 'delete':
6675
if (this.status === 'in_progress') {
67-
return ['Deleting', 'abcd.rb'];
76+
return `Deleting ${this.tool_arguments['path']!}`;
6877
} else {
69-
return ['Deleted', 'abcd.rb'];
78+
return `Deleted ${this.tool_arguments['path']!}`;
7079
}
7180

7281
case 'list':
7382
if (this.status === 'in_progress') {
74-
return ['Listing', ''];
83+
return 'Listing files';
7584
} else {
76-
return ['Listed', ''];
85+
return 'Listed files';
7786
}
7887

7988
case 'bash':
8089
if (this.status === 'in_progress') {
81-
return ['Running', 'tests'];
90+
return 'Running tests';
8291
} else {
83-
return ['Ran', 'tests'];
92+
return 'Ran tests';
8493
}
8594

8695
// This is a fake tool call that we insert for UI display purposes
8796
case 'analyze':
8897
if (this.status === 'in_progress') {
89-
return ['Analyzing', 'codebase'];
98+
return 'Analyzing codebase';
9099
} else {
91-
return ['Analyzed', 'codebase'];
100+
return 'Analyzed codebase';
92101
}
93102

94103
default:
95-
return [this.tool_name, ''];
104+
return this.tool_name;
96105
}
97106
}
107+
108+
isAnalysisAction(): boolean {
109+
return this.tool_name === 'analyze' || this.tool_name === 'read' || this.tool_name === 'list';
110+
}
98111
}
99112

100113
export default class LogstreamSection extends Component<Signature> {
@@ -141,10 +154,6 @@ export default class LogstreamSection extends Component<Signature> {
141154
get toolCalls(): ToolCall[] {
142155
const result: ToolCall[] = [];
143156

144-
if (this.events.length === 0) {
145-
result.push(new ToolCall('fake-tool-call-id', 'analyze', {}));
146-
}
147-
148157
for (const event of this.events) {
149158
if (event.event === 'tool_call_start') {
150159
result.push(new ToolCall(event.params.tool_call_id, event.params.tool_name, event.params.tool_arguments || {}));
@@ -153,6 +162,25 @@ export default class LogstreamSection extends Component<Signature> {
153162
}
154163
}
155164

165+
if (result.every((toolCall) => toolCall.isAnalysisAction())) {
166+
// Show an in-progress analysis action if all tool calls are analysis actions.
167+
result.push(new ToolCall('fake-tool-call-id', 'analyze', {}));
168+
} else {
169+
const analysisToolCall = new ToolCall('fake-tool-call-id', 'analyze', {});
170+
analysisToolCall.status = 'completed';
171+
172+
let lastAnalysisActionIndex = -1;
173+
174+
for (let i = result.length - 1; i >= 0; i--) {
175+
if (result[i]!.isAnalysisAction()) {
176+
lastAnalysisActionIndex = i;
177+
break;
178+
}
179+
}
180+
181+
result.splice(lastAnalysisActionIndex + 1, 0, analysisToolCall);
182+
}
183+
156184
return result;
157185
}
158186

@@ -161,6 +189,22 @@ export default class LogstreamSection extends Component<Signature> {
161189
this.eventsContainer = eventsContainer;
162190
}
163191

192+
// @ts-expect-error ember-animated not typed
193+
// eslint-disable-next-line require-yield
194+
*listTransition({ insertedSprites, keptSprites, removedSprites }) {
195+
for (const sprite of keptSprites) {
196+
move(sprite);
197+
}
198+
199+
for (const sprite of insertedSprites) {
200+
fadeIn(sprite);
201+
}
202+
203+
for (const sprite of removedSprites) {
204+
fadeOut(sprite);
205+
}
206+
}
207+
164208
willDestroy() {
165209
super.willDestroy();
166210

0 commit comments

Comments
 (0)