Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 277 additions & 49 deletions gui/src/components/AddProviderModal.tsx

Large diffs are not rendered by default.

248 changes: 151 additions & 97 deletions gui/src/components/provider-catalog/ProviderCatalog.tsx

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions gui/src/components/provider-catalog/provider-presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
import { providerTier, type ProviderTier, type WorkspaceProvider } from "../../provider-workspace/catalog";
import type { ProviderPayload } from "../../provider-payload";

export type ProviderAccessGroup =
| "recurring-or-keyless"
| "recurring-uncapped"
| "recurring-credit"
| "signup-credit";

export type ProviderSupportLevel = "supported" | "experimental" | "reference";

/** Row shape returned by GET /api/provider-presets (mirrors DerivedProviderPreset). */
export interface CatalogPreset {
id: string;
Expand All @@ -33,6 +41,53 @@ export interface CatalogPreset {
baseUrlChoices?: Array<{ id: string; label: string; baseUrl?: string }>;
codexAccountMode?: "direct" | "pool";
provider?: ProviderPayload;
accessGroups?: readonly ProviderAccessGroup[];
supportLevel?: ProviderSupportLevel;
verification?: "official" | "primary" | "unverified";
documentationUrl?: string;
modelsUrl?: string;
discovery?: "live" | "static" | "hybrid" | "unsupported";
lastVerified?: string;
models?: string[];
liveModels?: boolean;
}

export const ACCESS_GROUPS: ProviderAccessGroup[] = [
"recurring-or-keyless",
"recurring-uncapped",
"recurring-credit",
"signup-credit",
];

/** Curated free-directory rows. A provider may belong to more than one group. */
export function curatedPresets(presets: CatalogPreset[]): CatalogPreset[] {
return presets.filter(p => (p.accessGroups?.length ?? 0) > 0);
}

export function accessGroupCounts(presets: CatalogPreset[]): Record<ProviderAccessGroup, number> {
const counts = Object.fromEntries(ACCESS_GROUPS.map(group => [group, 0])) as Record<ProviderAccessGroup, number>;
for (const preset of curatedPresets(presets)) {
for (const group of new Set(preset.accessGroups)) counts[group] += 1;
}
return counts;
}

export function filterByAccessGroup(presets: CatalogPreset[], group: ProviderAccessGroup | "all"): CatalogPreset[] {
const curated = curatedPresets(presets);
return group === "all" ? curated : curated.filter(p => p.accessGroups?.includes(group));
}

/** Keep the requested directory at 81 while retaining legacy free/local presets in a separate lane. */
export function freeCatalogSections(presets: CatalogPreset[]): { directory: CatalogPreset[]; existing: CatalogPreset[] } {
const directory = curatedPresets(presets);
const free = bucketPresets(presets).free;
if (directory.length === 0) return { directory: free, existing: [] };
const directoryIds = new Set(directory.map(preset => preset.id));
return { directory, existing: free.filter(preset => !directoryIds.has(preset.id)) };
}

export function isPresetActionable(preset: CatalogPreset): boolean {
return preset.supportLevel !== "reference";
}

/**
Expand Down
38 changes: 38 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,8 @@ export const de = {
"modal.endpoint": "Endpunkt",
"modal.endpoint.tokenPlan": "Token-Plan",
"modal.endpoint.payAsYouGo": "Pay as you go",
"modal.endpoint.chinaMainland": "Festlandchina",
"modal.endpoint.international": "International",
"modal.endpoint.custom": "Benutzerdefiniert",
"modal.defaultModel": "Standardmodell (optional)",
"modal.allowPrivateNetwork": "Lokales/privates Netzwerk erlauben",
Expand Down Expand Up @@ -997,6 +999,42 @@ export const de = {
"time.minutesAgo": "vor {n} Min.",
"time.hoursAgo": "vor {n} Std.",
"time.daysAgo": "vor {n} T.",
"modal.catalogModes": "Zugriffsarten der Anbieter",
"modal.accessGroups": "Kostenlose Zugriffsgruppen",
"modal.providerDirectory": "Anbieterverzeichnis",
"modal.searchCount": "{count} Anbieter durchsuchen",
"modal.group.all": "Alle Anbieter",
"modal.group.recurring-or-keyless": "Regelmäßig / ohne Schlüssel",
"modal.group.recurring-uncapped": "Kein öffentliches Token-Limit",
"modal.group.recurring-credit": "Regelmäßiges Guthaben",
"modal.group.signup-credit": "Startguthaben",
"modal.group.existing": "Vorhandene kostenlose & lokale",
"modal.paidHint": "Kostenpflichtige Anbieter und Anbieter mit einmaligem Startguthaben durchsuchen.",
"modal.selectProvider": "Anbieter auswählen",
"modal.selectProviderHint": "Wähle einen Anbieter, um die Einrichtung anzusehen und Modelle zu suchen.",
"modal.badge.recurring": "Regelmäßig kostenlos",
"modal.badge.reference": "Referenz",
"modal.badge.experimental": "Experimentell",
"modal.badge.supported": "Unterstützt",
"modal.verification.official": "Offizielle Dokumentation",
"modal.verification.primary": "Primärquelle",
"modal.verification.unverified": "Nicht verifiziert",
"modal.providerDocs": "Anbieterdokumentation",
"modal.referenceExplanation": "Dieser Eintrag dient nur als Referenz. Automatische Aufrufe und die Modellsuche bleiben deaktiviert, bis ein dokumentierter Transportweg verifiziert wurde.",
"modal.modelDiscovery": "Modellsuche",
"modal.modelDiscoveryHint": "Verfügbare Modelle für diese Einrichtung suchen.",
"modal.discoverModels": "Modelle suchen",
"modal.openRouterFreeOnly": "Nur kostenlose OpenRouter-Modelle verwenden",
"modal.discovering": "Suche läuft…",
"modal.modelsLive": "Live-Antwort des Anbieters",
"modal.modelsStatic": "Statischer Katalog",
"modal.discoveredModels": "Gefundene Modelle",
"modal.discoveryFailed": "Für diese Einrichtung konnten keine Modelle gefunden werden.",
"modal.backToDirectory": "Zurück zum Anbieterverzeichnis",
"modal.setupReviewProvider": "Einrichtungsdokumentation für {label} prüfen.",
"modal.setupReferenceStep": "Nutze diesen Eintrag als Einrichtungshilfe, bis verifizierte Aufrufunterstützung verfügbar ist.",
"modal.setupKeylessStep": "Bestätige den Endpunkt unten; ein API-Schlüssel ist optional.",
"modal.setupDiscoverStep": "Modelle suchen und ein Standardmodell auswählen.",
"modal.noMatch": "Kein Treffer.",
"modal.oauthDefaultNote": "Mit deinem Konto anmelden — kein API-Schlüssel nötig.",
"modal.oauthComingSoon": "OAuth-Login für {label} kommt im nächsten Update. Nutze vorerst einen API-Schlüssel.",
Expand Down
38 changes: 38 additions & 0 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,8 @@ export const en = {
"modal.endpoint": "Endpoint",
"modal.endpoint.tokenPlan": "Token plan",
"modal.endpoint.payAsYouGo": "Pay as you go",
"modal.endpoint.chinaMainland": "China mainland",
"modal.endpoint.international": "International",
"modal.endpoint.custom": "Custom",
"modal.defaultModel": "Default model (optional)",
"modal.allowPrivateNetwork": "Allow local/private network",
Expand Down Expand Up @@ -840,6 +842,42 @@ export const en = {
"time.minutesAgo": "{n}m ago",
"time.hoursAgo": "{n}h ago",
"time.daysAgo": "{n}d ago",
"modal.catalogModes": "Provider access modes",
"modal.accessGroups": "Free access groups",
"modal.providerDirectory": "Provider directory",
"modal.searchCount": "Search {count} providers",
"modal.group.all": "All providers",
"modal.group.recurring-or-keyless": "Recurring / keyless",
"modal.group.recurring-uncapped": "No public token cap",
"modal.group.recurring-credit": "Recurring credits",
"modal.group.signup-credit": "Signup credits",
"modal.group.existing": "Existing free & local",
"modal.paidHint": "Browse paid providers and providers with one-time signup credit.",
"modal.selectProvider": "Select a provider",
"modal.selectProviderHint": "Choose a provider to view setup and discover models.",
"modal.badge.recurring": "Free recurring",
"modal.badge.reference": "Reference",
"modal.badge.experimental": "Experimental",
"modal.badge.supported": "Supported",
"modal.verification.official": "Official docs",
"modal.verification.primary": "Primary source",
"modal.verification.unverified": "Unverified",
"modal.providerDocs": "Provider docs",
"modal.referenceExplanation": "This entry is reference-only. Automated calling and model discovery remain disabled until a documented transport has been verified.",
"modal.modelDiscovery": "Model discovery",
"modal.modelDiscoveryHint": "Find available models for this setup.",
"modal.discoverModels": "Discover models",
"modal.openRouterFreeOnly": "Use free OpenRouter models only",
"modal.discovering": "Discovering…",
"modal.modelsLive": "Live provider response",
"modal.modelsStatic": "Static catalog",
"modal.discoveredModels": "Discovered models",
"modal.discoveryFailed": "No models could be discovered for this setup.",
"modal.backToDirectory": "Back to provider directory",
"modal.setupReviewProvider": "Review the {label} setup documentation.",
"modal.setupReferenceStep": "Use this entry as setup guidance until verified calling support is available.",
"modal.setupKeylessStep": "Confirm the endpoint below; an API key is optional.",
"modal.setupDiscoverStep": "Discover and choose a default model.",
"modal.noMatch": "No match.",
"modal.oauthDefaultNote": "Log in with your account — no API key needed.",
"modal.oauthComingSoon": "OAuth login for {label} arrives in the next update. Use an API key for now.",
Expand Down
38 changes: 38 additions & 0 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,8 @@ export const ja: Record<TKey, string> = {
"modal.endpoint": "エンドポイント",
"modal.endpoint.tokenPlan": "トークンプラン",
"modal.endpoint.payAsYouGo": "従量課金",
"modal.endpoint.chinaMainland": "中国本土",
"modal.endpoint.international": "国際",
"modal.endpoint.custom": "カスタム",
"modal.defaultModel": "デフォルトモデル(任意)",
"modal.allowPrivateNetwork": "ローカル/プライベートネットワークを許可",
Expand Down Expand Up @@ -795,6 +797,42 @@ export const ja: Record<TKey, string> = {
"time.minutesAgo": "{n}分前",
"time.hoursAgo": "{n}時間前",
"time.daysAgo": "{n}日前",
"modal.catalogModes": "プロバイダーのアクセス方式",
"modal.accessGroups": "無料アクセスのグループ",
"modal.providerDirectory": "プロバイダーディレクトリ",
"modal.searchCount": "{count} 件のプロバイダーを検索",
"modal.group.all": "すべてのプロバイダー",
"modal.group.recurring-or-keyless": "定期無料 / キー不要",
"modal.group.recurring-uncapped": "公開トークン上限なし",
"modal.group.recurring-credit": "定期付与クレジット",
"modal.group.signup-credit": "登録時クレジット",
"modal.group.existing": "既存の無料・ローカル",
"modal.paidHint": "有料プロバイダーと、登録時に一度だけクレジットが付与されるプロバイダーを参照します。",
"modal.selectProvider": "プロバイダーを選択",
"modal.selectProviderHint": "プロバイダーを選択して、設定の確認とモデルの検出を行います。",
"modal.badge.recurring": "定期無料",
"modal.badge.reference": "参考情報",
"modal.badge.experimental": "試験的",
"modal.badge.supported": "対応済み",
"modal.verification.official": "公式ドキュメント",
"modal.verification.primary": "一次情報",
"modal.verification.unverified": "未検証",
"modal.providerDocs": "プロバイダーのドキュメント",
"modal.referenceExplanation": "この項目は参考情報のみです。文書化された通信方式が検証されるまで、自動呼び出しとモデル検出は無効です。",
"modal.modelDiscovery": "モデル検出",
"modal.modelDiscoveryHint": "この設定で利用可能なモデルを検索します。",
"modal.discoverModels": "モデルを検出",
"modal.openRouterFreeOnly": "OpenRouter の無料モデルのみを使用",
"modal.discovering": "検出中…",
"modal.modelsLive": "プロバイダーからのライブ応答",
"modal.modelsStatic": "静的カタログ",
"modal.discoveredModels": "検出されたモデル",
"modal.discoveryFailed": "この設定でモデルを検出できませんでした。",
"modal.backToDirectory": "プロバイダーディレクトリに戻る",
"modal.setupReviewProvider": "{label} の設定ドキュメントを確認します。",
"modal.setupReferenceStep": "検証済みの呼び出しに対応するまで、この項目を設定ガイドとして利用してください。",
"modal.setupKeylessStep": "下記のエンドポイントを確認します。API キーは任意です。",
"modal.setupDiscoverStep": "モデルを検出して、既定のモデルを選択します。",
"modal.noMatch": "一致なし。",
"modal.oauthDefaultNote": "アカウントでログイン — API キー不要です。",
"modal.oauthComingSoon": "{label} の OAuth ログインは次回の更新で対応予定です。今は API キーをお使いください。",
Expand Down
38 changes: 38 additions & 0 deletions gui/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@ export const ko: Record<TKey, string> = {
"modal.endpoint": "엔드포인트",
"modal.endpoint.tokenPlan": "토큰 플랜",
"modal.endpoint.payAsYouGo": "종량제",
"modal.endpoint.chinaMainland": "중국 본토",
"modal.endpoint.international": "국제",
"modal.endpoint.custom": "사용자 지정",
"modal.defaultModel": "기본 모델(선택)",
"modal.allowPrivateNetwork": "로컬/사설 네트워크 허용",
Expand Down Expand Up @@ -1019,6 +1021,42 @@ export const ko: Record<TKey, string> = {
"time.minutesAgo": "{n}분 전",
"time.hoursAgo": "{n}시간 전",
"time.daysAgo": "{n}일 전",
"modal.catalogModes": "프로바이더 접근 방식",
"modal.accessGroups": "무료 접근 그룹",
"modal.providerDirectory": "프로바이더 디렉터리",
"modal.searchCount": "프로바이더 {count}개 검색",
"modal.group.all": "모든 프로바이더",
"modal.group.recurring-or-keyless": "정기 무료 / 키 불필요",
"modal.group.recurring-uncapped": "공개 토큰 한도 없음",
"modal.group.recurring-credit": "정기 크레딧",
"modal.group.signup-credit": "가입 크레딧",
"modal.group.existing": "기존 무료 및 로컬",
"modal.paidHint": "유료 프로바이더와 가입 시 일회성 크레딧을 제공하는 프로바이더를 찾아보세요.",
"modal.selectProvider": "프로바이더 선택",
"modal.selectProviderHint": "프로바이더를 선택해 설정을 확인하고 모델을 검색하세요.",
"modal.badge.recurring": "정기 무료",
"modal.badge.reference": "참고 자료",
"modal.badge.experimental": "실험적",
"modal.badge.supported": "지원됨",
"modal.verification.official": "공식 문서",
"modal.verification.primary": "1차 출처",
"modal.verification.unverified": "검증되지 않음",
"modal.providerDocs": "프로바이더 문서",
"modal.referenceExplanation": "이 항목은 참고용입니다. 문서화된 전송 방식이 검증될 때까지 자동 호출과 모델 검색은 비활성화됩니다.",
"modal.modelDiscovery": "모델 검색",
"modal.modelDiscoveryHint": "이 설정에서 사용 가능한 모델을 찾습니다.",
"modal.discoverModels": "모델 검색",
"modal.openRouterFreeOnly": "무료 OpenRouter 모델만 사용",
"modal.discovering": "검색 중…",
"modal.modelsLive": "프로바이더 실시간 응답",
"modal.modelsStatic": "정적 카탈로그",
"modal.discoveredModels": "검색된 모델",
"modal.discoveryFailed": "이 설정에서 모델을 찾지 못했습니다.",
"modal.backToDirectory": "프로바이더 디렉터리로 돌아가기",
"modal.setupReviewProvider": "{label} 설정 문서를 확인하세요.",
"modal.setupReferenceStep": "검증된 호출 지원이 제공될 때까지 이 항목을 설정 안내로 사용하세요.",
"modal.setupKeylessStep": "아래 엔드포인트를 확인하세요. API 키는 선택 사항입니다.",
"modal.setupDiscoverStep": "모델을 검색하고 기본 모델을 선택하세요.",
"modal.noMatch": "일치 항목 없음.",
"modal.oauthDefaultNote": "계정으로 로그인 — API 키 불필요.",
"modal.oauthComingSoon": "{label} OAuth 로그인은 다음 업데이트에 제공됩니다. 지금은 API 키를 사용하세요.",
Expand Down
Loading
Loading