Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@
flex: 0 0 auto;
max-inline-size: 100%;
}

> [data-openbitfun-component='tooltip-trigger'] {
flex: 0 0 auto;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from 'react';
import { Button, Checkbox, Input, SearchField, StatusPill } from '@openbitfun/ui';
import { Button, Checkbox, Icon, IconButton, Input, SearchField, StatusPill, Tooltip } from '@openbitfun/ui';
import { ConfigPageRow } from '@/infrastructure/config/components/common';
import { configManager } from '@/infrastructure/config/services/ConfigManager';
import { useSettingsDraft } from '@/infrastructure/config/settingsDraftRegistry';
Expand All @@ -16,14 +16,15 @@ interface Props {
ready: boolean;
onModelsChange: (models: AIModelConfig[]) => void;
onManual: () => void;
onRemoveModel: (model: AIModelConfig, trigger?: HTMLElement) => void;
}

export function MagpieSourceSection(props: Props) {
const scope = useSyncExternalStore(onSurfaceActivated, getActiveSurfaceScope, getActiveSurfaceScope);
return <MagpieSourceEditor key={scope.epoch} {...props} />;
}

function MagpieSourceEditor({ models, ready, onModelsChange, onManual }: Props) {
function MagpieSourceEditor({ models, ready, onModelsChange, onManual, onRemoveModel }: Props) {
const { t, formatNumber } = useI18n('settings/models');
const [scope] = useState(getActiveSurfaceScope);
const panelId = useId();
Expand All @@ -46,10 +47,12 @@ function MagpieSourceEditor({ models, ready, onModelsChange, onManual }: Props)
const supported = useSyncExternalStore(
listener => peerConnectionManager.subscribe(listener), supportsMagpieDiscovery, supportsMagpieDiscovery,
);
const imported = new Set((connection?.models ?? models.filter(model =>
const activeConnection = connection ? connections.find(item => item.id === connection.id) : undefined;
const importedModels = activeConnection?.models ?? (connection ? [] : models.filter(model =>
magpieGatewayUrl(model.base_url) === magpieGatewayUrl(baseUrl)
&& (model.api_key?.trim() || MAGPIE_APP_KEY) === (apiKey.trim() || MAGPIE_APP_KEY)
&& (!model.auth || model.auth.type === 'api_key'))).map(model => model.model_name));
&& (!model.auth || model.auth.type === 'api_key')));
const imported = new Set(importedModels.map(model => model.model_name));
const catalogReady = result?.status === 'ready' || result?.status === 'empty';
const credentialsChanged = baseUrl !== baseline.baseUrl || apiKey !== baseline.apiKey;
const dirty = selected.length > 0 || credentialsChanged;
Expand Down Expand Up @@ -173,7 +176,27 @@ function MagpieSourceEditor({ models, ready, onModelsChange, onManual }: Props)
label={model.display_name || model.id} description={model.id}
onChange={event => setSelected(previous => event.target.checked
? [...previous, model.id] : previous.filter(id => id !== model.id))} />
{imported.has(model.id) && <StatusPill tone="neutral">{t('magpie.imported')}</StatusPill>}
{imported.has(model.id) && <>
<StatusPill tone="neutral">{t('magpie.imported')}</StatusPill>
<Tooltip content={t('providerSelection.removeModel')}>
<IconButton
size="xs"
shape="circle"
variant="quiet"
tone="danger"
aria-label={t('providerSelection.removeModelByName', { name: model.display_name || model.id })}
data-testid="settings-magpie-remove-model"
data-model-id={model.id}
disabled={saving}
icon={<Icon name="xmark" size="xs" />}
onClick={event => {
event.stopPropagation();
const config = importedModels.find(item => item.model_name === model.id);
if (config) onRemoveModel(config, event.currentTarget);
}}
/>
</Tooltip>
</>}
{model.id.startsWith('group/') && <StatusPill tone="info">{t('magpie.routingGroup')}</StatusPill>}
{issue && <StatusPill tone="warning">{importIssues[issue]}</StatusPill>}
</div>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ describe('ModelSettingsPage loading and provider actions', () => {
base_url: base, api_key: key, provider: 'openai', capabilities: ['text_chat', 'function_calling'] });
expect(inference).not.toHaveBeenCalled();
expect(panel().querySelector<HTMLInputElement>('input[type="checkbox"]')!.disabled).toBe(true);
expect(panel().querySelector<HTMLButtonElement>('[data-testid="settings-magpie-remove-model"]')?.dataset.modelId)
.toBe('provider/model-with-tools');
});

it.each(['empty', 'unreachable', 'access_denied', 'unrecognized', 'catalog_error'] as const)(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4864,7 +4864,8 @@ const ModelSettingsPage: React.FC = () => {
) : <span className="openbitfun-model-settings__pool-caption">{t('acquisition.noneConfigured')}</span>}
</ConfigPageRow>
<MagpieSourceSection models={aiModels} ready={modelConfigReady} onModelsChange={setAiModels}
onManual={() => requestEditorOpen('new-provider:magpie', () => handleSelectProvider('magpie'))} />
onManual={() => requestEditorOpen('new-provider:magpie', () => handleSelectProvider('magpie'))}
onRemoveModel={(config, trigger) => { void requestDelete(config, trigger); }} />
<ConfigPageRow multiline className="openbitfun-model-settings__acquisition-row"
label={(
<div className="openbitfun-model-settings__import-row">
Expand Down
Loading