Skip to content

Commit c17c522

Browse files
committed
Implement dedicated handlers and UI for adding and updating model providers
- create a new form 'EditModelProviderForm' and handle appropriately updating model providers - 'model provider' form flow is missing 'confirm discard unsaved changes' - move `apps/editor/src/views/shared/forms/EditModelProviderForm/EditModelProviderForm.tsx`  to `apps/editor/src/views/settings/frontend/forms`
1 parent bd64c2f commit c17c522

12 files changed

Lines changed: 529 additions & 45 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import * as vscode from 'vscode'
2+
import { SettingsProvider } from '../settings-provider'
3+
import { PROVIDERS } from '@/constants/providers'
4+
import { AddModelProviderMessage } from '@/views/settings/types/messages'
5+
6+
export const handle_add_model_provider = async (
7+
provider: SettingsProvider,
8+
message: AddModelProviderMessage
9+
): Promise<void> => {
10+
const custom_label = '$(edit) Custom endpoint...'
11+
const available_built_in = Object.entries(PROVIDERS)
12+
13+
const items: vscode.QuickPickItem[] = [
14+
{
15+
label: custom_label,
16+
description: 'You can use any OpenAI-API compatible provider'
17+
},
18+
{
19+
label: 'predefined endpoints',
20+
kind: vscode.QuickPickItemKind.Separator
21+
},
22+
...available_built_in.map(([id, info]) => ({
23+
label: id,
24+
detail: info.base_url
25+
}))
26+
]
27+
28+
const quick_pick = vscode.window.createQuickPick()
29+
quick_pick.items = items
30+
quick_pick.title = 'Model Providers'
31+
quick_pick.placeholder =
32+
'Choose a predefined provider or add a custom endpoint'
33+
34+
const close_button: vscode.QuickInputButton = {
35+
iconPath: new vscode.ThemeIcon('close'),
36+
tooltip: 'Close'
37+
}
38+
quick_pick.buttons = [close_button]
39+
40+
const choice = await new Promise<{ id?: string } | null>((resolve) => {
41+
quick_pick.onDidTriggerButton((button) => {
42+
if (button === close_button) {
43+
quick_pick.hide()
44+
}
45+
})
46+
quick_pick.onDidAccept(() => {
47+
const selected = quick_pick.selectedItems[0]
48+
quick_pick.hide()
49+
if (!selected) return resolve(null)
50+
51+
if (selected.label === custom_label) {
52+
resolve({})
53+
} else {
54+
resolve({ id: selected.label })
55+
}
56+
})
57+
quick_pick.onDidHide(() => {
58+
quick_pick.dispose()
59+
resolve(null)
60+
})
61+
quick_pick.show()
62+
})
63+
64+
if (!choice) return
65+
66+
let new_name = ''
67+
let new_base_url = ''
68+
69+
if (choice.id) {
70+
const name = choice.id as keyof typeof PROVIDERS
71+
const info = PROVIDERS[name]
72+
new_name = name
73+
new_base_url = info.base_url
74+
}
75+
76+
provider.postMessage({
77+
command: 'START_MODEL_PROVIDER_CREATION',
78+
provider: {
79+
name: new_name,
80+
base_url: new_base_url,
81+
api_key_mask: ''
82+
},
83+
insertion_index: message.insertion_index,
84+
create_on_top: message.create_on_top
85+
})
86+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import * as vscode from 'vscode'
2+
import { SettingsProvider } from '../settings-provider'
3+
import {
4+
ModelProvidersManager,
5+
ModelProvider
6+
} from '@/services/model-providers-manager'
7+
import { generate_unique_name } from '@/views/shared/utils/generate-unique-name'
8+
import { UpdateModelProviderMessage } from '@/views/settings/types/messages'
9+
import { dictionary } from '@shared/constants/dictionary'
10+
11+
export const handle_update_model_provider = async (
12+
provider: SettingsProvider,
13+
message: UpdateModelProviderMessage
14+
): Promise<void> => {
15+
const a = message.updating_model_provider
16+
const b = message.provider
17+
let has_changes = false
18+
19+
if (a && b) {
20+
has_changes =
21+
a.name !== b.name ||
22+
a.base_url !== b.base_url ||
23+
(b.api_key !== undefined && b.api_key !== '') ||
24+
b.is_api_key_cleared ||
25+
false
26+
} else if (message.is_new) {
27+
has_changes = true
28+
}
29+
30+
if (!has_changes && !message.is_new) {
31+
if (message.origin === 'cancel') {
32+
provider.postMessage({ command: 'MODEL_PROVIDER_UPDATED' })
33+
return
34+
}
35+
}
36+
37+
if (message.is_new && message.origin === 'cancel' && !has_changes) {
38+
provider.postMessage({ command: 'MODEL_PROVIDER_UPDATED' })
39+
return
40+
}
41+
42+
if (message.origin === 'cancel') {
43+
const discard_button = 'Discard'
44+
const result = await vscode.window.showWarningMessage(
45+
dictionary.information_message.CONFIRM_DISCARD_UNSAVED_CHANGES(
46+
'model provider'
47+
),
48+
{
49+
modal: true,
50+
detail:
51+
dictionary.information_message.UNSAVED_CHANGES_TO_ITEM_WILL_BE_LOST(
52+
'model provider'
53+
)
54+
},
55+
discard_button
56+
)
57+
58+
if (result != discard_button) {
59+
return
60+
}
61+
62+
provider.postMessage({ command: 'MODEL_PROVIDER_UPDATED' })
63+
return
64+
}
65+
66+
if (!message.provider.base_url.trim()) {
67+
vscode.window.showErrorMessage(
68+
'A Base URL is required for model providers.'
69+
)
70+
return
71+
}
72+
if (!message.provider.name.trim()) {
73+
vscode.window.showErrorMessage('A Name is required for model providers.')
74+
return
75+
}
76+
77+
const providers_manager = new ModelProvidersManager(provider.context)
78+
const model_providers = await providers_manager.get_model_providers()
79+
80+
const updated_providers = [...model_providers]
81+
let working_provider: ModelProvider
82+
83+
const normalize_base_url = (url: string): string => {
84+
return url.trim().replace(/\/+$/, '')
85+
}
86+
87+
if (!message.is_new) {
88+
const existing_index = updated_providers.findIndex(
89+
(p) => p.name == message.original_name
90+
)
91+
if (existing_index == -1) {
92+
vscode.window.showErrorMessage(
93+
dictionary.error_message.COULD_NOT_UPDATE_ITEM_NOT_FOUND(
94+
'model provider',
95+
message.original_name!
96+
)
97+
)
98+
return
99+
}
100+
101+
working_provider = { ...updated_providers[existing_index] }
102+
working_provider.name = message.provider.name.trim()
103+
working_provider.base_url = normalize_base_url(message.provider.base_url)
104+
105+
if (message.provider.is_api_key_cleared) {
106+
working_provider.api_key = ''
107+
} else if (message.provider.api_key !== undefined) {
108+
working_provider.api_key = message.provider.api_key.trim()
109+
}
110+
111+
working_provider.name = generate_unique_name(
112+
working_provider.name,
113+
updated_providers
114+
.filter((p) => p.name != message.original_name)
115+
.map((p) => p.name)
116+
)
117+
118+
updated_providers[existing_index] = working_provider
119+
120+
if (message.original_name != working_provider.name) {
121+
await providers_manager.update_model_provider_name_in_api_configurations({
122+
old_name: message.original_name!,
123+
new_name: working_provider.name
124+
})
125+
}
126+
} else {
127+
working_provider = {
128+
name: message.provider.name.trim(),
129+
base_url: normalize_base_url(message.provider.base_url),
130+
api_key: message.provider.api_key?.trim() || ''
131+
}
132+
133+
working_provider.name = generate_unique_name(
134+
working_provider.name,
135+
updated_providers.map((p) => p.name)
136+
)
137+
138+
if (message.create_on_top) {
139+
updated_providers.unshift(working_provider)
140+
} else if (message.insertion_index !== undefined) {
141+
updated_providers.splice(message.insertion_index, 0, working_provider)
142+
} else {
143+
updated_providers.push(working_provider)
144+
}
145+
}
146+
147+
await providers_manager.save_model_providers(updated_providers)
148+
149+
provider.postMessage({ command: 'MODEL_PROVIDER_UPDATED' })
150+
151+
const { handle_get_model_providers } =
152+
await import('./handle-get-model-providers')
153+
await handle_get_model_providers(provider)
154+
}

apps/editor/src/views/settings/backend/message-handlers/handle-upsert-model-provider.ts

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

apps/editor/src/views/settings/backend/message-handlers/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
export * from './handle-add-model-provider'
2+
export * from './handle-update-model-provider'
13
export * from './handle-get-model-providers'
24
export * from './handle-reorder-model-providers'
3-
export * from './handle-upsert-model-provider'
45
export * from './handle-delete-model-provider'
56
export * from './handle-set-default-api-configuration'
67
export * from './handle-select-default-api-configuration'

apps/editor/src/views/settings/backend/settings-provider.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
FrontendMessage
55
} from '@/views/settings/types/messages'
66
import {
7-
handle_upsert_model_provider,
7+
handle_add_model_provider,
8+
handle_update_model_provider,
89
handle_delete_model_provider,
910
handle_get_check_new_files,
1011
handle_get_clear_checks_in_workspace_behavior,
@@ -144,18 +145,11 @@ export class SettingsProvider {
144145
} else if (message.command == 'REORDER_MODEL_PROVIDERS') {
145146
await handle_reorder_model_providers(this, message)
146147
} else if (message.command == 'ADD_MODEL_PROVIDER') {
147-
await handle_upsert_model_provider({
148-
provider: this,
149-
insertion_index: message.insertion_index,
150-
create_on_top: message.create_on_top
151-
})
148+
await handle_add_model_provider(this, message)
152149
} else if (message.command == 'DELETE_MODEL_PROVIDER') {
153150
await handle_delete_model_provider(this, message)
154-
} else if (message.command == 'EDIT_CUSTOM_MODEL_PROVIDER') {
155-
await handle_upsert_model_provider({
156-
provider: this,
157-
provider_name: message.provider_name
158-
})
151+
} else if (message.command == 'UPDATE_MODEL_PROVIDER') {
152+
await handle_update_model_provider(this, message)
159153
} else if (message.command == 'GET_API_CONFIGURATIONS') {
160154
await handle_get_api_configurations(this)
161155
} else if (message.command == 'SET_DEFAULT_API_CONFIGURATION') {

apps/editor/src/views/settings/frontend/Settings.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import { BackendMessage } from '../types/messages'
55
import { Home, NavItem } from './Home/Home'
66
import { use_web_configuration_editing } from './hooks/use-web-configuration-editing'
77
import { use_api_configuration_editing } from './hooks/use-api-configuration-editing'
8+
import { use_model_provider_editing } from './hooks/use-model-provider-editing'
89
import { Modal as UiModal } from '@ui/components/editor/settings/Modal'
910
import { EditWebConfigurationForm } from '@/views/shared/forms/EditWebConfigurationForm'
1011
import { EditApiConfigurationForm } from '@/views/shared/forms/EditApiConfigurationForm'
12+
import { EditModelProviderForm } from './forms/EditModelProviderForm'
1113

1214
const vscode = acquireVsCodeApi()
1315

@@ -29,6 +31,15 @@ export const Settings = () => {
2931
edit_api_configuration_save_handler
3032
} = use_api_configuration_editing(vscode)
3133

34+
const {
35+
updating_model_provider,
36+
set_updating_model_provider,
37+
set_updated_model_provider,
38+
edit_model_provider_cancel_handler,
39+
edit_model_provider_save_handler,
40+
set_is_new_model_provider
41+
} = use_model_provider_editing(vscode)
42+
3243
const [scroll_to_section_on_load, set_scroll_to_section_on_load] =
3344
useState<NavItem>()
3445

@@ -165,7 +176,18 @@ export const Settings = () => {
165176
on_reorder_providers={settings_hook.handle_reorder_providers}
166177
on_add_provider={settings_hook.handle_add_provider}
167178
on_delete_provider={settings_hook.handle_delete_provider}
168-
on_edit_provider={settings_hook.handle_edit_provider}
179+
on_edit_provider={(provider_name) => {
180+
const provider = settings_hook.providers?.find(
181+
(p) => p.name == provider_name
182+
)
183+
if (provider) {
184+
set_updating_model_provider({
185+
original_name: provider.name,
186+
provider
187+
})
188+
set_is_new_model_provider(false)
189+
}
190+
}}
169191
on_set_default_api_configuration={
170192
settings_hook.handle_set_default_api_configuration
171193
}
@@ -271,6 +293,20 @@ export const Settings = () => {
271293
</UiModal.Form>
272294
</UiModal>
273295
)}
296+
{updating_model_provider && (
297+
<UiModal on_close={edit_model_provider_cancel_handler}>
298+
<UiModal.Form
299+
title="Edit Model Provider"
300+
on_save={edit_model_provider_save_handler}
301+
on_cancel={edit_model_provider_cancel_handler}
302+
>
303+
<EditModelProviderForm
304+
provider={updating_model_provider.provider}
305+
on_update={set_updated_model_provider}
306+
/>
307+
</UiModal.Form>
308+
</UiModal>
309+
)}
274310
</>
275311
)
276312
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.form {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 16px;
5+
padding: 0 12px 12px 12px;
6+
}
7+
8+
.clear {
9+
color: var(--vscode-textLink-foreground);
10+
white-space: nowrap;
11+
font-size: 12px;
12+
13+
&:disabled {
14+
opacity: 0.5;
15+
cursor: default;
16+
}
17+
18+
&:hover:not(:disabled) {
19+
text-decoration: underline;
20+
}
21+
}

0 commit comments

Comments
 (0)