Skip to content

Commit 6980623

Browse files
committed
Enable drag-and-drop reordering for API tool configurations in the UI and implement corresponding backend message handling and persistence
1 parent 60efb8e commit 6980623

9 files changed

Lines changed: 219 additions & 41 deletions

File tree

packages/ui/src/components/editor/Configurations/Configurations.module.scss

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,40 @@
4747
}
4848

4949
&__left {
50+
display: flex;
51+
align-items: center;
52+
gap: var(--padding-5px);
5053
overflow: hidden;
5154
white-space: nowrap;
52-
overflow: hidden;
5355
text-overflow: ellipsis;
56+
height: 100%;
57+
58+
&__drag_handle {
59+
display: flex;
60+
align-items: center;
61+
height: 100%;
62+
cursor: grab;
63+
opacity: 0.5;
64+
65+
&:active {
66+
cursor: grabbing;
67+
}
68+
69+
&--disabled {
70+
opacity: 0.3;
71+
cursor: auto;
72+
}
73+
}
5474

55-
> span:last-child {
56-
margin-left: 0.5em;
57-
opacity: 0.7;
58-
font-size: 0.9em;
75+
&__text {
76+
overflow: hidden;
77+
text-overflow: ellipsis;
78+
white-space: nowrap;
79+
> span:last-child {
80+
margin-left: 0.5em;
81+
opacity: 0.7;
82+
font-size: 0.9em;
83+
}
5984
}
6085
}
6186
}

packages/ui/src/components/editor/Configurations/Configurations.tsx

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import styles from './Configurations.module.scss'
22
import { DEFAULT_TEMPERATURE } from '@shared/constants/api-tools'
33
import cn from 'classnames'
44
import { dictionary } from '@shared/constants/dictionary'
5+
import { ReactSortable } from 'react-sortablejs'
56

67
export namespace Configurations {
78
export type Configuration = {
9+
id: string
810
model: string
911
provider: string
1012
temperature?: number
@@ -16,6 +18,7 @@ export namespace Configurations {
1618
api_mode: 'edit-context' | 'code-completions'
1719
configurations: Configuration[]
1820
on_configuration_click: (i: number) => void
21+
on_reorder?: (configurations: Configuration[]) => void
1922
selected_configuration_index?: number
2023
}
2124
}
@@ -30,42 +33,69 @@ export const Configurations: React.FC<Configurations.Props> = (props) => {
3033
</div>
3134

3235
<div className={styles.configurations}>
33-
{props.configurations.map((configuration, i) => {
34-
const description_parts = [configuration.provider]
35-
if (configuration.reasoning_effort) {
36-
description_parts.push(`${configuration.reasoning_effort}`)
37-
}
38-
if (
39-
configuration.temperature &&
40-
configuration.temperature != DEFAULT_TEMPERATURE[props.api_mode]
41-
) {
42-
description_parts.push(`${configuration.temperature}`)
43-
}
44-
if (configuration.cache_enabled) {
45-
description_parts.push('cache-enabled')
46-
}
36+
<ReactSortable
37+
list={props.configurations}
38+
setList={(new_state) => {
39+
if (props.on_reorder) {
40+
props.on_reorder(new_state)
41+
}
42+
}}
43+
animation={150}
44+
handle={`.${styles.configurations__item__left__drag_handle}`}
45+
disabled={!props.on_reorder}
46+
>
47+
{props.configurations.map((configuration, i) => {
48+
const description_parts = [configuration.provider]
49+
if (configuration.reasoning_effort) {
50+
description_parts.push(`${configuration.reasoning_effort}`)
51+
}
52+
if (
53+
configuration.temperature &&
54+
configuration.temperature != DEFAULT_TEMPERATURE[props.api_mode]
55+
) {
56+
description_parts.push(`${configuration.temperature}`)
57+
}
58+
if (configuration.cache_enabled) {
59+
description_parts.push('cache-enabled')
60+
}
4761

48-
const description = description_parts.join(' · ')
62+
const description = description_parts.join(' · ')
4963

50-
return (
51-
<div
52-
key={i}
53-
className={cn(styles.configurations__item, {
54-
[styles['configurations__item--highlighted']]:
55-
props.selected_configuration_index == i
56-
})}
57-
onClick={() => {
58-
props.on_configuration_click(i)
59-
}}
60-
role="button"
61-
>
62-
<div className={styles.configurations__item__left}>
63-
<span>{configuration.model}</span>
64-
<span>{description}</span>
64+
return (
65+
<div
66+
key={configuration.id}
67+
className={cn(styles.configurations__item, {
68+
[styles['configurations__item--highlighted']]:
69+
props.selected_configuration_index == i
70+
})}
71+
onClick={() => {
72+
props.on_configuration_click(i)
73+
}}
74+
role="button"
75+
>
76+
<div className={styles.configurations__item__left}>
77+
<div
78+
className={cn(
79+
styles.configurations__item__left__drag_handle,
80+
{
81+
[styles[
82+
'configurations__item__left__drag_handle--disabled'
83+
]]: !props.on_reorder
84+
}
85+
)}
86+
onClick={(e) => e.stopPropagation()}
87+
>
88+
<span className="codicon codicon-gripper" />
89+
</div>
90+
<div className={styles.configurations__item__left__text}>
91+
<span>{configuration.model}</span>
92+
<span>{description}</span>
93+
</div>
94+
</div>
6595
</div>
66-
</div>
67-
)
68-
})}
96+
)
97+
})}
98+
</ReactSortable>
6999
</div>
70100
</div>
71101
)

packages/vscode/src/views/panel/backend/message-handlers/handle-get-api-tool-configurations.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,17 @@ export const handle_get_api_tool_configurations = async (
3838
}
3939

4040
const configurations: { [T in ApiMode]?: ApiToolConfiguration[] } = {
41-
'edit-context': edit_context_configs,
41+
'edit-context': edit_context_configs.map((config) => ({
42+
...config,
43+
id: `${config.provider_name}:${config.model}:${config.temperature}:${
44+
config.reasoning_effort ?? ''
45+
}:${config.instructions_placement ?? ''}`
46+
})),
4247
'code-completions': code_completions_configs.map((config) => ({
4348
...config,
49+
id: `${config.provider_name}:${config.model}:${config.temperature}:${
50+
config.reasoning_effort ?? ''
51+
}`,
4452
is_default: is_config_default(config, default_code_completions_config)
4553
}))
4654
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { ViewProvider } from '@/views/panel/backend/view-provider'
2+
import { ReorderApiToolConfigurationsMessage } from '@/views/panel/types/messages'
3+
import {
4+
ModelProvidersManager,
5+
ToolConfig
6+
} from '@/services/model-providers-manager'
7+
8+
const generate_edit_context_id = (config: ToolConfig) =>
9+
`${config.provider_name}:${config.model}:${config.temperature}:${
10+
config.reasoning_effort ?? ''
11+
}:${config.instructions_placement ?? ''}`
12+
13+
const generate_code_completions_id = (config: ToolConfig) =>
14+
`${config.provider_name}:${config.model}:${config.temperature}:${
15+
config.reasoning_effort ?? ''
16+
}`
17+
18+
export const handle_reorder_api_tool_configurations = async (
19+
provider: ViewProvider,
20+
message: ReorderApiToolConfigurationsMessage
21+
): Promise<void> => {
22+
const providers_manager = new ModelProvidersManager(provider.context)
23+
const reordered_ids = message.configurations.map((p) => p.id)
24+
25+
if (message.mode === 'edit-context') {
26+
const current_configs =
27+
await providers_manager.get_edit_context_tool_configs()
28+
const reordered_configs = reordered_ids
29+
.map((id) => {
30+
const found = current_configs.find(
31+
(p) => generate_edit_context_id(p) === id
32+
)
33+
if (!found) {
34+
console.error(`Config with id ${id} not found during reorder.`)
35+
return null
36+
}
37+
return found
38+
})
39+
.filter((p): p is ToolConfig => p !== null)
40+
41+
if (reordered_configs.length === current_configs.length) {
42+
await providers_manager.save_edit_context_tool_configs(reordered_configs)
43+
}
44+
} else if (message.mode === 'code-completions') {
45+
const current_configs =
46+
await providers_manager.get_code_completions_tool_configs()
47+
const reordered_configs = reordered_ids
48+
.map((id) => {
49+
const found = current_configs.find(
50+
(p) => generate_code_completions_id(p) === id
51+
)
52+
if (!found) {
53+
console.error(`Config with id ${id} not found during reorder.`)
54+
return null
55+
}
56+
return found
57+
})
58+
.filter((p): p is ToolConfig => p !== null)
59+
60+
if (reordered_configs.length === current_configs.length) {
61+
await providers_manager.save_code_completions_tool_configs(
62+
reordered_configs
63+
)
64+
}
65+
}
66+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export * from './handle-save-mode-api'
3131
export * from './handle-get-version'
3232
export * from './handle-at-sign-quick-pick-for-preset-affix'
3333
export * from './handle-get-api-tool-configurations'
34+
export * from './handle-reorder-api-tool-configurations'
3435
export * from './handle-get-donations-visibility'
3536
export * from './handle-save-donations-visibility'
3637
export * from './handle-focus-on-file-in-review'

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
handle_show_prompt_template_quick_pick,
4141
handle_at_sign_quick_pick_for_preset_affix,
4242
handle_get_api_tool_configurations,
43+
handle_reorder_api_tool_configurations,
4344
handle_pick_open_router_model,
4445
handle_pick_chatbot,
4546
handle_get_donations_visibility,
@@ -411,6 +412,8 @@ export class ViewProvider implements vscode.WebviewViewProvider {
411412
}
412413
} else if (message.command == 'GET_API_TOOL_CONFIGURATIONS') {
413414
await handle_get_api_tool_configurations(this)
415+
} else if (message.command == 'REORDER_API_TOOL_CONFIGURATIONS') {
416+
await handle_reorder_api_tool_configurations(this, message)
414417
} else if (message.command == 'SAVE_WEB_MODE') {
415418
await handle_save_mode_web(this, message.mode)
416419
} else if (message.command == 'GET_API_MODE') {

packages/vscode/src/views/panel/frontend/main/Main.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from '@/views/panel/types/messages'
1616
import { ApiToolConfiguration } from '@/views/panel/types/messages'
1717
import { post_message } from '../utils/post_message'
18+
import { Configurations as UiConfigurations } from '@ui/components/editor/Configurations'
1819

1920
type Props = {
2021
vscode: any
@@ -337,6 +338,36 @@ export const Main: React.FC<Props> = (props) => {
337338
})
338339
}
339340

341+
const handle_configurations_reorder = (
342+
reordered_configs: (UiConfigurations.Configuration & { id: string })[]
343+
) => {
344+
if (all_configurations && props.api_mode) {
345+
const current_api_configs = all_configurations[props.api_mode]
346+
if (!current_api_configs) return
347+
348+
const reordered_api_tool_configs = reordered_configs
349+
.map((ui_config) => {
350+
return current_api_configs.find((c) => c.id === ui_config.id)!
351+
})
352+
.filter(Boolean)
353+
354+
if (reordered_api_tool_configs.length !== current_api_configs.length) {
355+
return
356+
}
357+
358+
set_all_configurations({
359+
...all_configurations,
360+
[props.api_mode]: reordered_api_tool_configs
361+
})
362+
363+
post_message(props.vscode, {
364+
command: 'REORDER_API_TOOL_CONFIGURATIONS',
365+
mode: props.api_mode,
366+
configurations: reordered_api_tool_configs
367+
})
368+
}
369+
}
370+
340371
const handle_create_preset = () => {
341372
post_message(props.vscode, {
342373
command: 'CREATE_PRESET'
@@ -554,6 +585,7 @@ export const Main: React.FC<Props> = (props) => {
554585
copy_to_clipboard={handle_copy_to_clipboard}
555586
configurations={configurations_for_current_mode || []}
556587
on_configuration_click={handle_configuration_click}
588+
on_configurations_reorder={handle_configurations_reorder}
557589
on_search_click={handle_search_click}
558590
on_at_sign_click={handle_at_sign_click}
559591
on_curly_braces_click={handle_curly_braces_click}

packages/vscode/src/views/panel/frontend/main/MainView/MainView.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ type Props = {
3939
presets: Preset[]
4040
configurations: ApiToolConfiguration[]
4141
on_configuration_click: (index: number) => void
42+
on_configurations_reorder: (
43+
reordered_configurations: UiConfigurations.Configuration[]
44+
) => void
4245
has_active_editor: boolean
4346
has_active_selection: boolean
4447
has_changes_to_commit: boolean
@@ -406,12 +409,12 @@ export const MainView: React.FC<Props> = (props) => {
406409
<UiConfigurations
407410
api_mode={props.api_mode}
408411
configurations={props.configurations.map((c) => ({
409-
model: c.model,
412+
...c,
410413
provider: c.provider_name,
411-
reasoning_effort: c.reasoning_effort,
412414
cache_enabled: c.instructions_placement == 'below-only'
413415
}))}
414416
on_configuration_click={props.on_configuration_click}
417+
on_reorder={props.on_configurations_reorder}
415418
selected_configuration_index={
416419
props.selected_configuration_index
417420
}

packages/vscode/src/views/panel/types/messages.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ export interface BaseMessage {
1010
}
1111

1212
export type ApiToolConfiguration = {
13+
id: string
1314
provider_type: string
1415
provider_name: string
1516
model: string
17+
temperature: number
1618
reasoning_effort?: string
1719
instructions_placement?: InstructionsPlacement
20+
is_default?: boolean
1821
}
1922

2023
// === FROM FRONTEND TO BACKEND ===
@@ -197,6 +200,12 @@ export interface GetApiToolConfigurationsMessage extends BaseMessage {
197200
command: 'GET_API_TOOL_CONFIGURATIONS'
198201
}
199202

203+
export interface ReorderApiToolConfigurationsMessage extends BaseMessage {
204+
command: 'REORDER_API_TOOL_CONFIGURATIONS'
205+
mode: ApiMode
206+
configurations: ApiToolConfiguration[]
207+
}
208+
200209
export interface GetVersionMessage extends BaseMessage {
201210
command: 'GET_VERSION'
202211
}
@@ -296,6 +305,7 @@ export type FrontendMessage =
296305
| GetApiModeMessage
297306
| SaveApiModeMessage
298307
| GetApiToolConfigurationsMessage
308+
| ReorderApiToolConfigurationsMessage
299309
| SaveDonationsVisibilityMessage
300310
| GetVersionMessage
301311
| CheckClipboardForApplyMessage

0 commit comments

Comments
 (0)