Skip to content

Commit fcce556

Browse files
committed
Add a "New checkpoint..." button to the Timeline component for easier checkpoint creation
1 parent 5c4085e commit fcce556

11 files changed

Lines changed: 131 additions & 63 deletions

File tree

packages/ui/src/components/editor/panel/Responses/Responses.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { useEffect, useState } from 'react'
21
import cn from 'classnames'
32
import dayjs from 'dayjs'
43
import relativeTime from 'dayjs/plugin/relativeTime'
54
import { ResponseHistoryItem } from '@shared/types/response-history-item'
5+
import { use_periodic_re_render } from '../../../../hooks/use-periodic-re-render'
66
import styles from './Responses.module.scss'
77

88
dayjs.extend(relativeTime)
@@ -17,16 +17,7 @@ type Props = {
1717

1818
export const Responses: React.FC<Props> = (props) => {
1919
// Re-render every minute to update the relative time of the history responses.
20-
const [, set_now] = useState(Date.now())
21-
useEffect(() => {
22-
const interval = setInterval(() => {
23-
set_now(Date.now())
24-
}, 60 * 1000)
25-
26-
return () => {
27-
clearInterval(interval)
28-
}
29-
}, [])
20+
use_periodic_re_render(60 * 1000)
3021

3122
if (props.response_history.length == 0) {
3223
return null

packages/ui/src/components/editor/panel/Timeline/Timeline.module.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
&__line {
7676
width: 1px;
7777
flex-grow: 1;
78+
margin: 0 9px;
7879
background-color: var(--cwc-border-color-dimmed);
7980
}
8081

packages/ui/src/components/editor/panel/Timeline/Timeline.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import cn from 'classnames'
33
import dayjs from 'dayjs'
44
import relativeTime from 'dayjs/plugin/relativeTime'
55
import localizedFormat from 'dayjs/plugin/localizedFormat'
6+
import { use_periodic_re_render } from '../../../../hooks/use-periodic-re-render'
67

78
dayjs.extend(relativeTime)
89
dayjs.extend(localizedFormat)
@@ -19,15 +20,36 @@ type Props = {
1920
items: TimelineItemProps[]
2021
on_toggle_starred: (id: string | number) => void
2122
on_label_click: (id: string | number) => void
23+
on_create_click?: () => void
2224
}
2325

2426
export const Timeline: React.FC<Props> = ({
2527
items,
2628
on_toggle_starred,
27-
on_label_click
29+
on_label_click,
30+
on_create_click
2831
}) => {
32+
// Re-render every minute to update the relative time of the timeline items.
33+
use_periodic_re_render(60 * 1000)
34+
2935
return (
3036
<div className={styles.timeline}>
37+
{on_create_click && (
38+
<div className={styles.item}>
39+
<div className={styles.item__time} />
40+
<div className={styles.item__connector}>
41+
<div className={styles.item__line} />
42+
</div>
43+
<div className={styles.item__content}>
44+
<div
45+
className={styles.item__content__label}
46+
onClick={on_create_click}
47+
>
48+
New checkpoint...
49+
</div>
50+
</div>
51+
</div>
52+
)}
3153
{items.map((item) => (
3254
<div key={item.id} className={styles.item}>
3355
<div
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useState, useEffect } from 'react'
2+
3+
export function use_periodic_re_render(ms: number) {
4+
const [, set_now] = useState(Date.now())
5+
6+
useEffect(() => {
7+
const interval = setInterval(() => {
8+
set_now(Date.now())
9+
}, ms)
10+
11+
return () => {
12+
clearInterval(interval)
13+
}
14+
}, [ms])
15+
}

packages/vscode/src/commands/checkpoints-command/actions/edit-checkpoint.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

packages/vscode/src/commands/checkpoints-command/checkpoints-command.ts

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const checkpoints_command = (params: {
2929
websites_provider: WebsitesProvider
3030
panel_provider: PanelProvider
3131
}): vscode.Disposable[] => {
32-
let activeDeleteOperation: {
32+
let active_delete_operation: {
3333
finalize: () => Promise<void>
3434
timestamp: number
3535
} | null = null
@@ -53,14 +53,16 @@ export const checkpoints_command = (params: {
5353
params.websites_provider
5454
)
5555
if (checkpoint) {
56-
vscode.commands.executeCommand('codeWebChat.checkpoints')
56+
vscode.commands.executeCommand('codeWebChat.checkpoints', {
57+
highlight_checkpoint: checkpoint
58+
})
5759
}
5860
}
5961
)
6062

6163
const checkpoints_command = vscode.commands.registerCommand(
6264
'codeWebChat.checkpoints',
63-
async () => {
65+
async (args?: { highlight_checkpoint?: Checkpoint }) => {
6466
if (
6567
!vscode.workspace.workspaceFolders ||
6668
vscode.workspace.workspaceFolders.length == 0
@@ -72,6 +74,7 @@ export const checkpoints_command = (params: {
7274
}
7375

7476
const show_quick_pick = async () => {
77+
let checkpoint_to_highlight = args?.highlight_checkpoint
7578
const quick_pick = vscode.window.createQuickPick<
7679
vscode.QuickPickItem & { id?: string; checkpoint?: Checkpoint }
7780
>()
@@ -196,6 +199,17 @@ export const checkpoints_command = (params: {
196199
}
197200

198201
update_view()
202+
if (checkpoint_to_highlight) {
203+
const item = quick_pick.items.find(
204+
(i) =>
205+
(i as any).checkpoint?.timestamp ===
206+
checkpoint_to_highlight?.timestamp
207+
)
208+
if (item) {
209+
quick_pick.activeItems = [item]
210+
checkpoint_to_highlight = undefined
211+
}
212+
}
199213
quick_pick.busy = false
200214
}
201215

@@ -214,7 +228,9 @@ export const checkpoints_command = (params: {
214228
params.websites_provider
215229
)
216230
if (checkpoint) {
217-
vscode.commands.executeCommand('codeWebChat.checkpoints')
231+
vscode.commands.executeCommand('codeWebChat.checkpoints', {
232+
highlight_checkpoint: checkpoint
233+
})
218234
}
219235
} else if (selected.id == 'revert-last') {
220236
quick_pick.hide()
@@ -271,6 +287,7 @@ export const checkpoints_command = (params: {
271287
)
272288

273289
if (confirmation == 'Clear All') {
290+
active_delete_operation = null
274291
await clear_all_checkpoints(params.context, params.panel_provider)
275292
vscode.window.showInformationMessage(
276293
dictionary.information_message.ALL_CHECKPOINTS_CLEARED
@@ -318,6 +335,7 @@ export const checkpoints_command = (params: {
318335
if (e.button.tooltip == 'Edit Description') {
319336
notification_count++
320337
const new_description = await vscode.window.showInputBox({
338+
title: 'Description',
321339
prompt: 'Enter a description for the checkpoint',
322340
value: item.checkpoint.description || '',
323341
placeHolder: 'e.g. Before refactoring the main component'
@@ -353,8 +371,8 @@ export const checkpoints_command = (params: {
353371
}
354372

355373
if (e.button.tooltip == 'Delete') {
356-
if (activeDeleteOperation) {
357-
await activeDeleteOperation.finalize()
374+
if (active_delete_operation) {
375+
await active_delete_operation.finalize()
358376
}
359377

360378
const deleted_checkpoint = item.checkpoint
@@ -398,7 +416,7 @@ export const checkpoints_command = (params: {
398416
}
399417
}
400418
}
401-
activeDeleteOperation = currentOperation
419+
active_delete_operation = currentOperation
402420

403421
notification_count++
404422
const choice = await vscode.window.showInformationMessage(
@@ -410,16 +428,22 @@ export const checkpoints_command = (params: {
410428
notification_count--
411429

412430
if (
413-
activeDeleteOperation &&
414-
activeDeleteOperation.timestamp === currentOperation.timestamp
431+
active_delete_operation &&
432+
active_delete_operation.timestamp === currentOperation.timestamp
415433
) {
416434
if (choice == 'Undo') {
417435
// Restore to state and UI
418-
checkpoints.splice(
419-
real_index_in_state,
420-
0,
421-
original_checkpoint_from_state
422-
)
436+
const current_checkpoints =
437+
params.context.workspaceState.get<Checkpoint[]>(
438+
CHECKPOINTS_STATE_KEY,
439+
[]
440+
) ?? []
441+
442+
current_checkpoints.push(original_checkpoint_from_state)
443+
current_checkpoints.sort((a, b) => b.timestamp - a.timestamp)
444+
445+
checkpoints = current_checkpoints
446+
423447
await params.context.workspaceState.update(
424448
CHECKPOINTS_STATE_KEY,
425449
checkpoints
@@ -434,10 +458,20 @@ export const checkpoints_command = (params: {
434458
notification_count--
435459
})
436460
update_view()
461+
const restored_item = quick_pick.items.find(
462+
(i) =>
463+
(i as any).checkpoint?.timestamp ==
464+
original_checkpoint_from_state.timestamp
465+
)
466+
if (restored_item) {
467+
quick_pick.activeItems = [restored_item]
468+
}
469+
quick_pick.show()
437470
} else {
438471
await currentOperation.finalize()
472+
quick_pick.dispose()
439473
}
440-
activeDeleteOperation = null
474+
active_delete_operation = null
441475
} else if (choice === 'Undo') {
442476
notification_count++
443477
vscode.window
@@ -448,9 +482,9 @@ export const checkpoints_command = (params: {
448482
.then(() => {
449483
notification_count--
450484
})
485+
quick_pick.show()
451486
}
452487

453-
quick_pick.show()
454488
return
455489
}
456490
})
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as vscode from 'vscode'
2+
import { create_checkpoint } from '@/commands/checkpoints-command/actions'
3+
import type { PanelProvider } from '../panel-provider'
4+
import { dictionary } from '@shared/constants/dictionary'
5+
6+
export const handle_create_checkpoint = async (
7+
panel_provider: PanelProvider
8+
) => {
9+
if (
10+
!vscode.workspace.workspaceFolders ||
11+
vscode.workspace.workspaceFolders.length == 0
12+
) {
13+
vscode.window.showErrorMessage(
14+
dictionary.error_message.CHECKPOINTS_ONLY_IN_WORKSPACE
15+
)
16+
return
17+
}
18+
await create_checkpoint(
19+
panel_provider.workspace_provider,
20+
panel_provider.context,
21+
panel_provider,
22+
panel_provider.websites_provider
23+
)
24+
}

packages/vscode/src/views/panel/backend/message-handlers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './handle-copy-prompt'
22
export * from './handle-apply-response-from-history'
33
export * from './handle-create-preset'
4+
export * from './handle-create-checkpoint'
45
export * from './handle-create-group'
56
export * from './handle-create-separator'
67
export * from './handle-delete-preset'

packages/vscode/src/views/panel/backend/panel-provider.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
handle_send_prompt,
1919
handle_update_preset,
2020
handle_delete_preset,
21+
handle_create_checkpoint,
2122
handle_delete_group,
2223
handle_delete_separator,
2324
handle_duplicate_preset,
@@ -425,6 +426,8 @@ export class PanelProvider implements vscode.WebviewViewProvider {
425426
try {
426427
if (message.command == 'GET_CHECKPOINTS') {
427428
await this.send_checkpoints()
429+
} else if (message.command == 'CREATE_CHECKPOINT') {
430+
await handle_create_checkpoint(this)
428431
} else if (message.command == 'TOGGLE_CHECKPOINT_STAR') {
429432
await toggle_checkpoint_star({
430433
context: this.context,

packages/vscode/src/views/panel/frontend/Home/Home.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ export const Home: React.FC<Props> = (props) => {
3636
} as FrontendMessage)
3737
}
3838

39+
const handle_create_checkpoint_click = () => {
40+
post_message(props.vscode, {
41+
command: 'CREATE_CHECKPOINT'
42+
} as FrontendMessage)
43+
}
44+
3945
return (
4046
<>
4147
<div className={styles.header}>
@@ -108,6 +114,7 @@ export const Home: React.FC<Props> = (props) => {
108114
on_label_click={(id) =>
109115
props.on_restore_checkpoint(id as number)
110116
}
117+
on_create_click={handle_create_checkpoint_click}
111118
/>
112119
</div>
113120
</div>

0 commit comments

Comments
 (0)