Skip to content

Commit 9b1ae29

Browse files
committed
Add Models.dev extended provider picker and extract shared provider source selection
- Below 'custom endpoint' place 'Show extended list' (desc: load providers from Models.dev, globe action link: https://models.dev/). Once chosen, download https://models.dev/api.json, shema is like here: [Pasted text]. Use only these 3 keys of each provider: [Pasted text] - the logic responsible for showing extended list of providers (models.dev) should be located in `apps/editor/src/views/shared/actions/api` - Extract the provider selection QuickPick logic duplicated across `upsert-provider.ts` and `handle-add-provider.ts` into a reusable `pick_provider_source` helper with full i18n support. - rename 'show extended list' to 'Show all...' (load Models.dev database), change icon to 'cloud-download' - add missing translations [Fragment] - add translations to [Fragment] - models.dev entries sometimes miss 'api' key, such should be excluded - statically type models.dev reponse to what we use - models.dev downloaded providers should be filtered out if not having doc - don't filter our wihtoud doc, just don't show doc action - prevent focus out on models.dev list └── apps └── editor └── src ├── constants │ └── providers.ts ├── i18n │ └── translations │ └── views │ └── shared │ └── actions │ └── api │ ├── common.ts │ ├── create │ │ ├── create.ts │ │ ├── index.ts │ │ └── interactions │ │ ├── index.ts │ │ ├── initial-select-model.ts │ │ ├── initial-select-provider.ts │ │ └── verify-model.ts │ ├── delete.ts │ ├── index.ts │ ├── pick-extended-provider.ts │ ├── pick-reasoning-effort.ts │ ├── update │ │ ├── index.ts │ │ └── interactions │ │ ├── edit-model-for-api-config.ts │ │ ├── edit-reasoning-effort-for-api-config.ts │ │ └── index.ts │ └── upsert-provider.ts └── views ├── prompt │ └── backend │ └── message-handlers │ └── handle-pick-reasoning-effort.ts ├── settings │ └── backend │ └── message-handlers │ ├── handle-add-provider.ts │ └── handle-pick-reasoning-effort.ts └── shared └── actions └── api ├── create │ ├── create.ts │ └── interactions │ ├── index.ts │ ├── initial-select-model.ts │ ├── initial-select-provider.ts │ ├── verify-model.ts │ └── verify-reasoning-effort.ts ├── delete.ts ├── pick-extended-provider.ts ├── pick-provider-source.ts ├── pick-reasoning-effort.ts ├── reorder.ts ├── update │ ├── interactions │ │ ├── edit-model-for-api-config.ts │ │ ├── edit-provider-for-api-config.ts │ │ ├── edit-reasoning-effort-for-api-config.ts │ │ └── index.ts │ └── update.ts └── upsert-provider.ts
1 parent 1b2790f commit 9b1ae29

7 files changed

Lines changed: 356 additions & 164 deletions

File tree

‎apps/editor/src/i18n/translations/views/shared/actions/api/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { translations as common } from './common'
22
import { translations as create } from './create'
33
import { translations as pick_reasoning_effort } from './pick-reasoning-effort'
4+
import { translations as pick_extended_provider } from './pick-extended-provider'
45
import { translations as update } from './update'
56
import { translations as upsert_provider } from './upsert-provider'
67
import { translations as delete_translations } from './delete'
@@ -9,6 +10,7 @@ export const translations = {
910
...common,
1011
...create,
1112
...pick_reasoning_effort,
13+
...pick_extended_provider,
1214
...update,
1315
...upsert_provider,
1416
...delete_translations
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const translations = {
2+
'views.shared.actions.api.pick-extended-provider.error': {
3+
en: 'Failed to load providers from Models.dev',
4+
pl: 'Nie udało się załadować dostawców z Models.dev',
5+
'zh-cn': '无法从 Models.dev 加载提供商',
6+
ja: 'Models.dev からプロバイダーを読み込めませんでした',
7+
'zh-tw': '無法從 Models.dev 載入提供商',
8+
de: 'Fehler beim Laden der Anbieter von Models.dev',
9+
es: 'Error al cargar los proveedores de Models.dev',
10+
fr: 'Échec du chargement des fournisseurs depuis Models.dev',
11+
'pt-br': 'Falha ao carregar provedores do Models.dev',
12+
ru: 'Не удалось загрузить провайдеров из Models.dev',
13+
ko: 'Models.dev에서 제공자를 로드하지 못했습니다',
14+
it: 'Impossibile caricare i fornitori da Models.dev',
15+
tr: "Sağlayıcılar Models.dev'den yüklenemedi",
16+
cs: 'Nepodařilo se načíst poskytovatele z Models.dev',
17+
hu: 'Nem sikerült betölteni a szolgáltatókat a Models.dev oldalról',
18+
bg: 'Неуспешно зареждане на доставчици от Models.dev'
19+
}
20+
} as const
21+

‎apps/editor/src/i18n/translations/views/shared/actions/api/upsert-provider.ts‎

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -378,22 +378,94 @@ export const translations = {
378378
bg: '$(edit) Персонализирана крайна точка...'
379379
},
380380
'views.shared.actions.api.upsert-provider.options.custom-desc': {
381-
en: 'You can use any OpenAI-API compatible provider',
382-
pl: 'Możesz użyć dowolnego dostawcy kompatybilnego z API OpenAI',
383-
'zh-cn': '您可以使用任何与 OpenAI API 兼容的提供商',
384-
ja: 'OpenAI APIと互換性のあるプロバイダーならどれでも使用できます',
385-
'zh-tw': '您可以使用任何與 OpenAI API 相容的提供商',
386-
de: 'Sie können jeden OpenAI-API-kompatiblen Anbieter verwenden',
387-
es: 'Puedes utilizar cualquier proveedor compatible con la API de OpenAI',
388-
fr: "Vous pouvez utiliser n'importe quel fournisseur compatible avec l'API OpenAI",
389-
'pt-br': 'Você pode usar qualquer provedor compatível com a API da OpenAI',
390-
ru: 'Вы можете использовать любого провайдера, совместимого с OpenAI API',
391-
ko: 'OpenAI API 호환 제공자를 사용할 수 있습니다',
392-
it: "Puoi usare qualsiasi provider compatibile con l'API di OpenAI",
393-
tr: 'OpenAI API uyumlu herhangi bir sağlayıcıyı kullanabilirsiniz',
394-
cs: 'Můžete použít jakéhokoli poskytovatele kompatibilního s OpenAI API',
395-
hu: 'Bármilyen OpenAI API-kompatibilis szolgáltatót használhat',
396-
bg: 'Можете да използвате всеки доставчик, съвместим с OpenAI API'
381+
en: 'Use any OpenAI-API compatible enpoint',
382+
pl: 'Użyj dowolnego endpointa zgodnego z OpenAI API',
383+
'zh-cn': '使用任何与 OpenAI API 兼容的端点',
384+
ja: 'OpenAI API 互換の任意のエンドポイントを使用',
385+
'zh-tw': '使用任何與 OpenAI API 相容的端點',
386+
de: 'Beliebigen OpenAI-API-kompatiblen Endpunkt verwenden',
387+
es: 'Usa cualquier punto de enlace compatible con la API de OpenAI',
388+
fr: "Utilisez n'importe quel point de terminaison compatible avec l'API OpenAI",
389+
'pt-br': 'Use qualquer endpoint compatível com a API OpenAI',
390+
ru: 'Используйте любой эндпоинт, совместимый с OpenAI API',
391+
ko: 'OpenAI API와 호환되는 모든 엔드포인트 사용',
392+
it: "Usa qualsiasi endpoint compatibile con l'API OpenAI",
393+
tr: 'OpenAI API uyumlu herhangi bir uç nokta kullanın',
394+
cs: 'Použijte jakýkoli koncový bod kompatibilní s OpenAI API',
395+
hu: 'Használjon bármilyen OpenAI API-kompatibilis végpontot',
396+
bg: 'Използвайте всяка крайна точка, съвместима с OpenAI API'
397+
},
398+
'views.shared.actions.api.upsert-provider.options.extended-label': {
399+
en: '$(cloud-download) Show all...',
400+
pl: '$(cloud-download) Pokaż wszystkie...',
401+
'zh-cn': '$(cloud-download) 显示全部...',
402+
ja: '$(cloud-download) すべて表示...',
403+
'zh-tw': '$(cloud-download) 顯示全部...',
404+
de: '$(cloud-download) Alle anzeigen...',
405+
es: '$(cloud-download) Mostrar todo...',
406+
fr: '$(cloud-download) Tout afficher...',
407+
'pt-br': '$(cloud-download) Mostrar todos...',
408+
ru: '$(cloud-download) Показать все...',
409+
ko: '$(cloud-download) 모두 표시...',
410+
it: '$(cloud-download) Mostra tutto...',
411+
tr: '$(cloud-download) Tümünü göster...',
412+
cs: '$(cloud-download) Zobrazit vše...',
413+
hu: '$(cloud-download) Összes megjelenítése...',
414+
bg: '$(cloud-download) Покажи всички...'
415+
},
416+
'views.shared.actions.api.upsert-provider.options.extended-desc': {
417+
en: 'Load Models.dev database',
418+
pl: 'Pobierz z bazy danych Models.dev',
419+
'zh-cn': '加载 Models.dev 数据库',
420+
ja: 'Models.dev データベースを読み込む',
421+
'zh-tw': '載入 Models.dev 資料庫',
422+
de: 'Models.dev-Datenbank laden',
423+
es: 'Cargar base de datos de Models.dev',
424+
fr: 'Charger la base de données Models.dev',
425+
'pt-br': 'Carregar banco de dados do Models.dev',
426+
ru: 'Загрузить базу данных Models.dev',
427+
ko: 'Models.dev 데이터베이스 로드',
428+
it: 'Carica database di Models.dev',
429+
tr: 'Models.dev veritabanını yükle',
430+
cs: 'Načíst databázi Models.dev',
431+
hu: 'Models.dev adatbázis betöltése',
432+
bg: 'Зареждане на базата данни на Models.dev'
433+
},
434+
'views.shared.actions.api.upsert-provider.options.extended-title': {
435+
en: 'Extended Providers',
436+
pl: 'Rozszerzeni dostawcy',
437+
'zh-cn': '扩展提供商',
438+
ja: '拡張プロバイダー',
439+
'zh-tw': '擴展提供商',
440+
de: 'Erweiterte Anbieter',
441+
es: 'Proveedores extendidos',
442+
fr: 'Fournisseurs étendus',
443+
'pt-br': 'Provedores Estendidos',
444+
ru: 'Расширенные провайдеры',
445+
ko: '확장된 제공자',
446+
it: 'Fornitori estesi',
447+
tr: 'Genişletilmiş Sağlayıcılar',
448+
cs: 'Rozšíření poskytovatelé',
449+
hu: 'Bővített szolgáltatók',
450+
bg: 'Разширени доставчици'
451+
},
452+
'views.shared.actions.api.upsert-provider.options.extended-placeholder': {
453+
en: 'Choose a provider from Models.dev',
454+
pl: 'Wybierz dostawcę z Models.dev',
455+
'zh-cn': '从 Models.dev 中选择提供商',
456+
ja: 'Models.devからプロバイダーを選択',
457+
'zh-tw': '從 Models.dev 中選擇提供商',
458+
de: 'Wählen Sie einen Anbieter von Models.dev',
459+
es: 'Elige un proveedor de Models.dev',
460+
fr: 'Choisissez un fournisseur depuis Models.dev',
461+
'pt-br': 'Escolha um provedor do Models.dev',
462+
ru: 'Выберите провайдера из Models.dev',
463+
ko: 'Models.dev에서 제공자를 선택하세요',
464+
it: 'Scegli un fornitore da Models.dev',
465+
tr: "Models.dev'den bir sağlayıcı seçin",
466+
cs: 'Vyberte poskytovatele z Models.dev',
467+
hu: 'Válasszon szolgáltatót a Models.dev webhelyről',
468+
bg: 'Изберете доставчик от Models.dev'
397469
},
398470
'views.shared.actions.api.upsert-provider.options.predefined': {
399471
en: 'predefined endpoints',
@@ -459,8 +531,7 @@ export const translations = {
459531
de: 'Für benutzerdefinierte Anbieter ist eine Basis-URL erforderlich.',
460532
es: 'Se requiere una URL base para los proveedores personalizados.',
461533
fr: 'Une URL de base est requise pour les fournisseurs personnalisés.',
462-
'pt-br':
463-
'Uma URL base é obrigatória para provedores personalizados.',
534+
'pt-br': 'Uma URL base é obrigatória para provedores personalizados.',
464535
ru: 'Базовый URL обязателен для пользовательских провайдеров.',
465536
ko: '사용자 지정 제공자에는 기본 URL이 필요합니다.',
466537
it: 'Un URL di base è obbligatorio per i fornitori personalizzati.',

‎apps/editor/src/views/settings/backend/message-handlers/handle-add-provider.ts‎

Lines changed: 32 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { SettingsViewProvider } from '../settings-view-provider'
33
import { PROVIDERS } from '@/constants/providers'
44
import { AddProviderMessage } from '@/views/settings/types/messages'
55
import { t } from '@/i18n'
6+
import { pick_extended_provider } from '@/views/shared/actions/api/pick-extended-provider'
7+
import { pick_provider_source } from '@/views/shared/actions/api/pick-provider-source'
68

79
export const handle_add_provider = async (
810
provider: SettingsViewProvider,
@@ -66,80 +68,42 @@ export const handle_add_provider = async (
6668
: message.insertion_index + 1
6769
}
6870

69-
const custom_label = '$(edit) Custom endpoint...'
70-
const available_built_in = Object.entries(PROVIDERS)
71+
while (true) {
72+
const choice = await pick_provider_source()
7173

72-
const items: vscode.QuickPickItem[] = [
73-
{
74-
label: custom_label,
75-
description: 'You can use any OpenAI-API compatible provider'
76-
},
77-
{
78-
label: 'predefined endpoints',
79-
kind: vscode.QuickPickItemKind.Separator
80-
},
81-
...available_built_in.map(([id, info]) => ({
82-
label: id,
83-
detail: info.base_url
84-
}))
85-
]
74+
if (choice === 'BACK' || !choice) return
8675

87-
const quick_pick = vscode.window.createQuickPick()
88-
quick_pick.items = items
89-
quick_pick.title = 'Providers'
90-
quick_pick.placeholder =
91-
'Choose a predefined provider or add a custom endpoint'
76+
let new_name = ''
77+
let new_base_url = ''
9278

93-
const close_button: vscode.QuickInputButton = {
94-
iconPath: new vscode.ThemeIcon('close'),
95-
tooltip: 'Close'
96-
}
97-
quick_pick.buttons = [close_button]
79+
if (choice.is_extended) {
80+
const ext_choice = await pick_extended_provider()
9881

99-
const choice = await new Promise<{ id?: string } | null>((resolve) => {
100-
quick_pick.onDidTriggerButton((button) => {
101-
if (button === close_button) {
102-
quick_pick.hide()
82+
if (ext_choice === 'BACK') {
83+
continue
10384
}
104-
})
105-
quick_pick.onDidAccept(() => {
106-
const selected = quick_pick.selectedItems[0]
107-
quick_pick.hide()
108-
if (!selected) return resolve(null)
10985

110-
if (selected.label === custom_label) {
111-
resolve({})
112-
} else {
113-
resolve({ id: selected.label })
114-
}
115-
})
116-
quick_pick.onDidHide(() => {
117-
quick_pick.dispose()
118-
resolve(null)
86+
if (!ext_choice) return
87+
88+
new_name = ext_choice.name
89+
new_base_url = ext_choice.url
90+
} else if (choice.id) {
91+
const name = choice.id as keyof typeof PROVIDERS
92+
const info = PROVIDERS[name]
93+
new_name = name
94+
new_base_url = info.base_url
95+
}
96+
97+
provider.postMessage({
98+
command: 'START_PROVIDER_CREATION',
99+
provider: {
100+
name: new_name,
101+
base_url: new_base_url,
102+
api_key_mask: '',
103+
extended_cache: undefined
104+
},
105+
insertion_index
119106
})
120-
quick_pick.show()
121-
})
122-
123-
if (!choice) return
124-
125-
let new_name = ''
126-
let new_base_url = ''
127-
128-
if (choice.id) {
129-
const name = choice.id as keyof typeof PROVIDERS
130-
const info = PROVIDERS[name]
131-
new_name = name
132-
new_base_url = info.base_url
107+
break
133108
}
134-
135-
provider.postMessage({
136-
command: 'START_PROVIDER_CREATION',
137-
provider: {
138-
name: new_name,
139-
base_url: new_base_url,
140-
api_key_mask: '',
141-
extended_cache: undefined
142-
},
143-
insertion_index
144-
})
145109
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import * as vscode from 'vscode'
2+
import axios from 'axios'
3+
import { t } from '@/i18n'
4+
5+
type ModelsDevProvider = {
6+
name: string
7+
doc?: string
8+
api?: string
9+
}
10+
11+
type ModelsDevResponse = Record<string, ModelsDevProvider>
12+
13+
export const pick_extended_provider = async (): Promise<
14+
{ name: string; url: string } | null | 'BACK'
15+
> => {
16+
try {
17+
const response = await axios.get<ModelsDevResponse>(
18+
'https://models.dev/api.json'
19+
)
20+
const providers_data = response.data
21+
22+
const extended_items: vscode.QuickPickItem[] = Object.values(providers_data)
23+
.filter((p) => p && p.api)
24+
.map((p) => ({
25+
label: p.name,
26+
detail: p.api,
27+
buttons: p.doc
28+
? [
29+
{
30+
iconPath: new vscode.ThemeIcon('globe'),
31+
tooltip: p.doc
32+
}
33+
]
34+
: undefined
35+
}))
36+
37+
const extended_quick_pick = vscode.window.createQuickPick()
38+
extended_quick_pick.ignoreFocusOut = true
39+
extended_quick_pick.items = extended_items
40+
extended_quick_pick.title = t(
41+
'views.shared.actions.api.upsert-provider.options.extended-title'
42+
)
43+
extended_quick_pick.placeholder = t(
44+
'views.shared.actions.api.upsert-provider.options.extended-placeholder'
45+
)
46+
47+
const close_button: vscode.QuickInputButton = {
48+
iconPath: new vscode.ThemeIcon('close'),
49+
tooltip: t('common.close')
50+
}
51+
extended_quick_pick.buttons = [vscode.QuickInputButtons.Back, close_button]
52+
53+
const ext_choice = await new Promise<
54+
{ name: string; url: string } | null | 'BACK'
55+
>((resolve) => {
56+
extended_quick_pick.onDidTriggerButton((button) => {
57+
if (button === close_button) {
58+
extended_quick_pick.hide()
59+
} else if (button === vscode.QuickInputButtons.Back) {
60+
resolve('BACK')
61+
extended_quick_pick.hide()
62+
}
63+
})
64+
extended_quick_pick.onDidTriggerItemButton((e) => {
65+
const button = e.button
66+
if (button.tooltip) {
67+
vscode.env.openExternal(vscode.Uri.parse(button.tooltip))
68+
}
69+
})
70+
extended_quick_pick.onDidAccept(() => {
71+
const selected = extended_quick_pick.selectedItems[0]
72+
extended_quick_pick.hide()
73+
if (!selected) return resolve(null)
74+
resolve({ name: selected.label, url: selected.detail || '' })
75+
})
76+
extended_quick_pick.onDidHide(() => {
77+
extended_quick_pick.dispose()
78+
resolve(null)
79+
})
80+
extended_quick_pick.show()
81+
})
82+
83+
return ext_choice
84+
} catch (e) {
85+
vscode.window.showErrorMessage(
86+
t('views.shared.actions.api.pick-extended-provider.error')
87+
)
88+
return 'BACK'
89+
}
90+
}

0 commit comments

Comments
 (0)