fix(runtime-provider): clarify opencode model routes ux
This commit is contained in:
parent
2b3a184bef
commit
c04871747c
5 changed files with 249 additions and 88 deletions
|
|
@ -55,16 +55,24 @@
|
|||
"emptyRecommended": "No recommended models found.",
|
||||
"emptyRecommendedFree": "No recommended free models found.",
|
||||
"freeOnly": "Free only",
|
||||
"launchableDescription": "Routes you can test or use in the team picker: local config, free built-in models, and current default.",
|
||||
"launchableTitle": "Launchable OpenCode models",
|
||||
"launchableDescription": "Known routes from OpenCode config, free built-in models, and the current default. Local routes need a successful test before they are ready for team launches.",
|
||||
"launchableTitle": "OpenCode model routes",
|
||||
"loadingRoutes": "Loading OpenCode model routes...",
|
||||
"noRoutesMatch": "No OpenCode model routes match \"{{query}}\".",
|
||||
"noneReported": "No launchable OpenCode model routes were reported yet. Configure a local route in OpenCode or use the Providers tab to inspect catalog providers.",
|
||||
"noneReported": "No OpenCode model routes were reported yet. Configure a local route in OpenCode or use the Providers tab to inspect catalog providers.",
|
||||
"recommendedOnly": "Recommended only",
|
||||
"searchPlaceholder": "Search models",
|
||||
"selectProjectBeforeTesting": "Select a project context before testing models.",
|
||||
"selectProjectBeforeTestingDefaults": "Select a project context before testing or saving OpenCode defaults.",
|
||||
"useInTeamPicker": "Use in team picker"
|
||||
"testInProgress": "Model test is already running.",
|
||||
"useInTeamPicker": "Save for team picker",
|
||||
"validationContextRequired": "Select a validation context above to enable Test and Set default. Saving for team picker only stores the route for new teams.",
|
||||
"actionsUnavailable": "Actions are temporarily unavailable.",
|
||||
"defaultSaveInProgress": "OpenCode default is being saved.",
|
||||
"routeUnavailableAuth": "This provider requires authentication before this model can be used.",
|
||||
"routeUnavailableFailed": "This model route failed its last execution test.",
|
||||
"routeUnavailableGeneric": "This model route cannot be used right now.",
|
||||
"routeUnavailableUnknown": "This model is the current OpenCode default, but it is not available in the live catalog yet."
|
||||
},
|
||||
"providers": {
|
||||
"catalog": "OpenCode provider catalog",
|
||||
|
|
@ -99,10 +107,11 @@
|
|||
"searchPlaceholder": "Search model routes"
|
||||
},
|
||||
"badges": {
|
||||
"usedInTeamPicker": "Used in team picker",
|
||||
"usedInTeamPicker": "Saved for team picker",
|
||||
"free": "free",
|
||||
"local": "local",
|
||||
"configured": "configured",
|
||||
"knownRoute": "known route",
|
||||
"connected": "connected",
|
||||
"verified": "verified",
|
||||
"needsTest": "needs test",
|
||||
|
|
|
|||
|
|
@ -55,16 +55,24 @@
|
|||
"emptyRecommended": "Recommended models не найдены.",
|
||||
"emptyRecommendedFree": "Recommended free models не найдены.",
|
||||
"freeOnly": "Только free",
|
||||
"launchableDescription": "Routes, которые можно тестировать или использовать в team picker: local config, free built-in models и текущий default.",
|
||||
"launchableTitle": "Launchable OpenCode models",
|
||||
"launchableDescription": "Известные routes из OpenCode config, free built-in models и текущий default. Local routes нужно проверить тестом перед запуском команд.",
|
||||
"launchableTitle": "Маршруты моделей OpenCode",
|
||||
"loadingRoutes": "Загрузка OpenCode model routes...",
|
||||
"noRoutesMatch": "OpenCode model routes не найдены по запросу \"{{query}}\".",
|
||||
"noneReported": "Launchable OpenCode model routes пока не получены. Настройте local route в OpenCode или используйте вкладку Providers для просмотра catalog providers.",
|
||||
"noneReported": "OpenCode model routes пока не получены. Настройте local route в OpenCode или используйте вкладку Providers для просмотра catalog providers.",
|
||||
"recommendedOnly": "Только recommended",
|
||||
"searchPlaceholder": "Поиск моделей",
|
||||
"selectProjectBeforeTesting": "Выберите project context перед тестированием моделей.",
|
||||
"selectProjectBeforeTestingDefaults": "Выберите project context перед тестированием или сохранением OpenCode defaults.",
|
||||
"useInTeamPicker": "Использовать в team picker"
|
||||
"testInProgress": "Тест модели уже выполняется.",
|
||||
"useInTeamPicker": "Сохранить для team picker",
|
||||
"validationContextRequired": "Выберите validation context выше, чтобы включить Test и Set default. Сохранение для team picker только запоминает route для новых команд.",
|
||||
"actionsUnavailable": "Действия временно недоступны.",
|
||||
"defaultSaveInProgress": "OpenCode default сохраняется.",
|
||||
"routeUnavailableAuth": "Этому provider нужна авторизация перед использованием модели.",
|
||||
"routeUnavailableFailed": "Этот model route не прошёл последний execution test.",
|
||||
"routeUnavailableGeneric": "Этот model route сейчас нельзя использовать.",
|
||||
"routeUnavailableUnknown": "Эта модель выбрана текущим OpenCode default, но её пока нет в live catalog."
|
||||
},
|
||||
"providers": {
|
||||
"catalog": "OpenCode provider catalog",
|
||||
|
|
@ -99,10 +107,11 @@
|
|||
"searchPlaceholder": "Поиск маршрутов моделей"
|
||||
},
|
||||
"badges": {
|
||||
"usedInTeamPicker": "Используется в выборе команды",
|
||||
"usedInTeamPicker": "Сохранено для team picker",
|
||||
"free": "free",
|
||||
"local": "local",
|
||||
"configured": "настроено",
|
||||
"knownRoute": "известный route",
|
||||
"connected": "подключено",
|
||||
"verified": "проверено",
|
||||
"needsTest": "нужен тест",
|
||||
|
|
|
|||
|
|
@ -2852,10 +2852,11 @@ export default interface Resources {
|
|||
default: 'default';
|
||||
failed: 'failed';
|
||||
free: 'free';
|
||||
knownRoute: 'known route';
|
||||
local: 'local';
|
||||
needsTest: 'needs test';
|
||||
unknown: 'unknown';
|
||||
usedInTeamPicker: 'Used in team picker';
|
||||
usedInTeamPicker: 'Saved for team picker';
|
||||
verified: 'verified';
|
||||
};
|
||||
compatibleEndpoint: {
|
||||
|
|
@ -2889,22 +2890,30 @@ export default interface Resources {
|
|||
searchPlaceholder: 'Search model routes';
|
||||
};
|
||||
models: {
|
||||
actionsUnavailable: 'Actions are temporarily unavailable.';
|
||||
alreadyDefault: 'This is already the selected OpenCode default.';
|
||||
defaultSaveInProgress: 'OpenCode default is being saved.';
|
||||
empty: 'No models found.';
|
||||
emptyFree: 'No free models found.';
|
||||
emptyRecommended: 'No recommended models found.';
|
||||
emptyRecommendedFree: 'No recommended free models found.';
|
||||
freeOnly: 'Free only';
|
||||
launchableDescription: 'Routes you can test or use in the team picker: local config, free built-in models, and current default.';
|
||||
launchableTitle: 'Launchable OpenCode models';
|
||||
launchableDescription: 'Known routes from OpenCode config, free built-in models, and the current default. Local routes need a successful test before they are ready for team launches.';
|
||||
launchableTitle: 'OpenCode model routes';
|
||||
loadingRoutes: 'Loading OpenCode model routes...';
|
||||
noRoutesMatch: 'No OpenCode model routes match "{{query}}".';
|
||||
noneReported: 'No launchable OpenCode model routes were reported yet. Configure a local route in OpenCode or use the Providers tab to inspect catalog providers.';
|
||||
noneReported: 'No OpenCode model routes were reported yet. Configure a local route in OpenCode or use the Providers tab to inspect catalog providers.';
|
||||
recommendedOnly: 'Recommended only';
|
||||
routeUnavailableAuth: 'This provider requires authentication before this model can be used.';
|
||||
routeUnavailableFailed: 'This model route failed its last execution test.';
|
||||
routeUnavailableGeneric: 'This model route cannot be used right now.';
|
||||
routeUnavailableUnknown: 'This model is the current OpenCode default, but it is not available in the live catalog yet.';
|
||||
searchPlaceholder: 'Search models';
|
||||
selectProjectBeforeTesting: 'Select a project context before testing models.';
|
||||
selectProjectBeforeTestingDefaults: 'Select a project context before testing or saving OpenCode defaults.';
|
||||
useInTeamPicker: 'Use in team picker';
|
||||
testInProgress: 'Model test is already running.';
|
||||
useInTeamPicker: 'Save for team picker';
|
||||
validationContextRequired: 'Select a validation context above to enable Test and Set default. Saving for team picker only stores the route for new teams.';
|
||||
};
|
||||
providers: {
|
||||
catalog: 'OpenCode provider catalog';
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ import {
|
|||
SelectValue,
|
||||
} from '@renderer/components/ui/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@renderer/components/ui/tooltip';
|
||||
import { cn } from '@renderer/lib/utils';
|
||||
import {
|
||||
compareOpenCodeTeamModelRecommendations,
|
||||
|
|
@ -212,6 +218,33 @@ function isDefaultForScope(
|
|||
return scopedDefault === model.modelId;
|
||||
}
|
||||
|
||||
const DisabledActionTooltip = ({
|
||||
reason,
|
||||
children,
|
||||
}: {
|
||||
readonly reason: string | undefined;
|
||||
readonly children: JSX.Element;
|
||||
}): JSX.Element => {
|
||||
if (!reason) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={150}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-flex" title={reason} aria-label={reason}>
|
||||
{children}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-72 text-pretty text-xs leading-relaxed">
|
||||
{reason}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
function directoryEntryMatchesQuery(
|
||||
provider: RuntimeProviderDirectoryEntryDto,
|
||||
query: string
|
||||
|
|
@ -1449,7 +1482,7 @@ function ModelBadges({
|
|||
{t('runtimeProvider.badges.local')}
|
||||
</Badge>
|
||||
<Badge className="bg-sky-400/15 px-1.5 py-0 text-[10px] text-sky-200">
|
||||
{t('runtimeProvider.badges.configured')}
|
||||
{t('runtimeProvider.badges.knownRoute')}
|
||||
</Badge>
|
||||
</>
|
||||
) : null}
|
||||
|
|
@ -1517,17 +1550,47 @@ function canUseOpenCodeModelRoute(model: RuntimeProviderModelDto): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
function getOpenCodeRouteUnavailableTitle(model: RuntimeProviderModelDto): string | undefined {
|
||||
function getOpenCodeRouteUnavailableTitle(
|
||||
model: RuntimeProviderModelDto,
|
||||
t: SettingsT
|
||||
): string | undefined {
|
||||
if (isUnknownOpenCodeModelRoute(model)) {
|
||||
return 'This model is the current OpenCode default, but it is not available in the live catalog yet.';
|
||||
return t('runtimeProvider.models.routeUnavailableUnknown');
|
||||
}
|
||||
if (model.accessKind === 'not_authenticated') {
|
||||
return (
|
||||
model.accessReason ?? 'This provider requires authentication before this model can be used.'
|
||||
);
|
||||
return model.accessReason ?? t('runtimeProvider.models.routeUnavailableAuth');
|
||||
}
|
||||
if (model.accessKind === 'execution_failed' || model.proofState === 'failed') {
|
||||
return model.accessReason ?? 'This model route failed its last execution test.';
|
||||
return model.accessReason ?? t('runtimeProvider.models.routeUnavailableFailed');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getDisabledActionReason(input: {
|
||||
readonly disabled: boolean;
|
||||
readonly contextRequiredTitle?: string;
|
||||
readonly unavailableTitle?: string;
|
||||
readonly busy: boolean;
|
||||
readonly busyTitle: string;
|
||||
readonly alreadyDefault?: boolean;
|
||||
readonly alreadyDefaultTitle?: string;
|
||||
readonly capabilityAvailable: boolean;
|
||||
readonly t: SettingsT;
|
||||
}): string | undefined {
|
||||
if (input.disabled) {
|
||||
return input.t('runtimeProvider.models.actionsUnavailable');
|
||||
}
|
||||
if (input.contextRequiredTitle) {
|
||||
return input.contextRequiredTitle;
|
||||
}
|
||||
if (input.busy) {
|
||||
return input.busyTitle;
|
||||
}
|
||||
if (input.alreadyDefault) {
|
||||
return input.alreadyDefaultTitle;
|
||||
}
|
||||
if (!input.capabilityAvailable) {
|
||||
return input.unavailableTitle ?? input.t('runtimeProvider.models.routeUnavailableGeneric');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -1863,6 +1926,18 @@ function ConfiguredOpenCodeModelsPanel({
|
|||
</div>
|
||||
|
||||
<div className="mt-3 space-y-2">
|
||||
{!hasProjectContext ? (
|
||||
<div
|
||||
className="rounded-md border px-3 py-2 text-xs leading-5"
|
||||
style={{
|
||||
borderColor: 'rgba(251, 191, 36, 0.28)',
|
||||
backgroundColor: 'rgba(251, 191, 36, 0.08)',
|
||||
color: '#fde68a',
|
||||
}}
|
||||
>
|
||||
{t('runtimeProvider.models.validationContextRequired')}
|
||||
</div>
|
||||
) : null}
|
||||
{visibleModels.length === 0 ? (
|
||||
<div className="rounded-md border border-dashed border-white/10 px-3 py-3 text-sm text-[var(--color-text-muted)]">
|
||||
{t('runtimeProvider.models.noRoutesMatch', { query: query.trim() })}
|
||||
|
|
@ -1873,20 +1948,55 @@ function ConfiguredOpenCodeModelsPanel({
|
|||
const testing = state.testingModelIds.includes(model.modelId);
|
||||
const savingDefault = state.savingDefaultModelId === model.modelId;
|
||||
const result = state.modelResults[model.modelId];
|
||||
const unavailableTitle = getOpenCodeRouteUnavailableTitle(model);
|
||||
const unavailableTitle = getOpenCodeRouteUnavailableTitle(model, t);
|
||||
const contextRequiredTitle = hasProjectContext
|
||||
? undefined
|
||||
: t('runtimeProvider.models.selectProjectBeforeTestingDefaults');
|
||||
const alreadyDefaultForScope = isDefaultForScope(model, state, defaultScope);
|
||||
const canTest =
|
||||
!disabled && hasProjectContext && !testing && canTestOpenCodeModelRoute(model);
|
||||
const canUse = !disabled && canUseOpenCodeModelRoute(model);
|
||||
const testCapabilityAvailable = canTestOpenCodeModelRoute(model);
|
||||
const useCapabilityAvailable = canUseOpenCodeModelRoute(model);
|
||||
const canTest = !disabled && hasProjectContext && !testing && testCapabilityAvailable;
|
||||
const canUse = !disabled && useCapabilityAvailable;
|
||||
const canSetDefault =
|
||||
!disabled &&
|
||||
hasProjectContext &&
|
||||
!savingDefault &&
|
||||
!alreadyDefaultForScope &&
|
||||
canUseOpenCodeModelRoute(model);
|
||||
useCapabilityAvailable;
|
||||
const testDisabledReason = canTest
|
||||
? undefined
|
||||
: getDisabledActionReason({
|
||||
disabled,
|
||||
contextRequiredTitle,
|
||||
unavailableTitle,
|
||||
busy: testing,
|
||||
busyTitle: t('runtimeProvider.models.testInProgress'),
|
||||
capabilityAvailable: testCapabilityAvailable,
|
||||
t,
|
||||
});
|
||||
const useDisabledReason = canUse
|
||||
? undefined
|
||||
: getDisabledActionReason({
|
||||
disabled,
|
||||
unavailableTitle,
|
||||
busy: false,
|
||||
busyTitle: '',
|
||||
capabilityAvailable: useCapabilityAvailable,
|
||||
t,
|
||||
});
|
||||
const setDefaultDisabledReason = canSetDefault
|
||||
? undefined
|
||||
: getDisabledActionReason({
|
||||
disabled,
|
||||
contextRequiredTitle,
|
||||
unavailableTitle,
|
||||
busy: savingDefault,
|
||||
busyTitle: t('runtimeProvider.models.defaultSaveInProgress'),
|
||||
alreadyDefault: alreadyDefaultForScope,
|
||||
alreadyDefaultTitle: t('runtimeProvider.models.alreadyDefault'),
|
||||
capabilityAvailable: useCapabilityAvailable,
|
||||
t,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
key={model.modelId}
|
||||
|
|
@ -1912,61 +2022,57 @@ function ConfiguredOpenCodeModelsPanel({
|
|||
<ModelBadges model={model} usedForNewTeams={selected} />
|
||||
</div>
|
||||
<div className="flex shrink-0 flex-wrap justify-end gap-1.5">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-8"
|
||||
disabled={!canTest}
|
||||
title={canTest ? undefined : (contextRequiredTitle ?? unavailableTitle)}
|
||||
onClick={() => {
|
||||
if (!canTest) return;
|
||||
void actions.testModel(model.providerId, model.modelId);
|
||||
}}
|
||||
>
|
||||
{testing ? (
|
||||
<Loader2 className="mr-1 size-3.5 animate-spin" />
|
||||
) : (
|
||||
<CheckCircle2 className="mr-1 size-3.5" />
|
||||
)}
|
||||
{t('runtimeProvider.actions.test')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8"
|
||||
disabled={!canUse}
|
||||
title={canUse ? undefined : unavailableTitle}
|
||||
onClick={() => {
|
||||
if (!canUse) return;
|
||||
actions.useModelForNewTeams(model.modelId);
|
||||
}}
|
||||
>
|
||||
{t('runtimeProvider.models.useInTeamPicker')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8"
|
||||
disabled={!canSetDefault}
|
||||
title={
|
||||
canSetDefault
|
||||
? undefined
|
||||
: (contextRequiredTitle ??
|
||||
(alreadyDefaultForScope
|
||||
? t('runtimeProvider.models.alreadyDefault')
|
||||
: unavailableTitle))
|
||||
}
|
||||
onClick={() => {
|
||||
if (!canSetDefault) return;
|
||||
void actions.setDefaultModel(model.providerId, model.modelId, defaultScope);
|
||||
}}
|
||||
>
|
||||
{savingDefault ? <Loader2 className="mr-1 size-3.5 animate-spin" /> : null}
|
||||
{getDefaultScopeButtonLabel(defaultScope, t)}
|
||||
</Button>
|
||||
<DisabledActionTooltip reason={testDisabledReason}>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="h-8"
|
||||
disabled={!canTest}
|
||||
onClick={() => {
|
||||
if (!canTest) return;
|
||||
void actions.testModel(model.providerId, model.modelId);
|
||||
}}
|
||||
>
|
||||
{testing ? (
|
||||
<Loader2 className="mr-1 size-3.5 animate-spin" />
|
||||
) : (
|
||||
<CheckCircle2 className="mr-1 size-3.5" />
|
||||
)}
|
||||
{t('runtimeProvider.actions.test')}
|
||||
</Button>
|
||||
</DisabledActionTooltip>
|
||||
<DisabledActionTooltip reason={useDisabledReason}>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8"
|
||||
disabled={!canUse}
|
||||
onClick={() => {
|
||||
if (!canUse) return;
|
||||
actions.useModelForNewTeams(model.modelId);
|
||||
}}
|
||||
>
|
||||
{t('runtimeProvider.models.useInTeamPicker')}
|
||||
</Button>
|
||||
</DisabledActionTooltip>
|
||||
<DisabledActionTooltip reason={setDefaultDisabledReason}>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-8"
|
||||
disabled={!canSetDefault}
|
||||
onClick={() => {
|
||||
if (!canSetDefault) return;
|
||||
void actions.setDefaultModel(model.providerId, model.modelId, defaultScope);
|
||||
}}
|
||||
>
|
||||
{savingDefault ? <Loader2 className="mr-1 size-3.5 animate-spin" /> : null}
|
||||
{getDefaultScopeButtonLabel(defaultScope, t)}
|
||||
</Button>
|
||||
</DisabledActionTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<ModelResult result={result} />
|
||||
|
|
|
|||
|
|
@ -653,9 +653,10 @@ describe('RuntimeProviderManagementPanelView', () => {
|
|||
const row = host.querySelector<HTMLElement>(
|
||||
'[data-testid="configured-opencode-model-row-llama.cpp/qwen-test:0.5b"]'
|
||||
);
|
||||
expect(host.textContent).toContain('Launchable OpenCode models');
|
||||
expect(host.textContent).toContain('OpenCode model routes');
|
||||
expect(host.textContent).toContain('Known routes from OpenCode config');
|
||||
expect(row?.textContent).toContain('local');
|
||||
expect(row?.textContent).toContain('configured');
|
||||
expect(row?.textContent).toContain('known route');
|
||||
expect(row?.textContent).toContain('needs test');
|
||||
|
||||
const buttons = Array.from(row?.querySelectorAll('button') ?? []);
|
||||
|
|
@ -664,7 +665,7 @@ describe('RuntimeProviderManagementPanelView', () => {
|
|||
await Promise.resolve();
|
||||
});
|
||||
await act(async () => {
|
||||
buttons.find((button) => button.textContent?.includes('Use in team picker'))?.click();
|
||||
buttons.find((button) => button.textContent?.includes('Save for team picker'))?.click();
|
||||
await Promise.resolve();
|
||||
});
|
||||
await act(async () => {
|
||||
|
|
@ -847,10 +848,30 @@ describe('RuntimeProviderManagementPanelView', () => {
|
|||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(host.textContent).toContain('Launchable OpenCode models');
|
||||
expect(host.textContent).toContain('OpenCode model routes');
|
||||
expect(host.textContent).toContain('llama.cpp/qwen-test:0.5b');
|
||||
expect(host.textContent).toContain(
|
||||
'Select a validation context above to enable Test and Set default'
|
||||
);
|
||||
expect(host.textContent).toContain('Providers');
|
||||
expect(host.querySelector('[data-testid="runtime-provider-row-openrouter"]')).toBeNull();
|
||||
|
||||
const row = host.querySelector<HTMLElement>(
|
||||
'[data-testid="configured-opencode-model-row-llama.cpp/qwen-test:0.5b"]'
|
||||
);
|
||||
const buttons = Array.from(row?.querySelectorAll('button') ?? []);
|
||||
expect(buttons.map((button) => [button.textContent?.trim(), button.disabled])).toEqual([
|
||||
['Test', true],
|
||||
['Save for team picker', false],
|
||||
['Set all-projects default', true],
|
||||
]);
|
||||
expect(
|
||||
Array.from(row?.querySelectorAll('[title]') ?? []).some(
|
||||
(element) =>
|
||||
element.getAttribute('title') ===
|
||||
'Select a project context before testing or saving OpenCode defaults.'
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('shows unknown OpenCode defaults without enabling launch actions', async () => {
|
||||
|
|
@ -897,6 +918,13 @@ describe('RuntimeProviderManagementPanelView', () => {
|
|||
|
||||
const buttons = Array.from(row?.querySelectorAll('button') ?? []);
|
||||
expect(buttons.map((button) => button.disabled)).toEqual([true, true, true]);
|
||||
expect(
|
||||
Array.from(row?.querySelectorAll('[title]') ?? []).some(
|
||||
(element) =>
|
||||
element.getAttribute('title') ===
|
||||
'This model is the current OpenCode default, but it is not available in the live catalog yet.'
|
||||
)
|
||||
).toBe(true);
|
||||
await act(async () => {
|
||||
buttons.forEach((button) => button.click());
|
||||
await Promise.resolve();
|
||||
|
|
@ -1807,7 +1835,7 @@ describe('RuntimeProviderManagementPanelView', () => {
|
|||
});
|
||||
|
||||
expect(host.textContent).toContain('openrouter/openai/gpt-oss-20b:free');
|
||||
expect(host.textContent).toContain('Used in team picker');
|
||||
expect(host.textContent).toContain('Saved for team picker');
|
||||
expect(host.textContent).toContain('Model probe passed');
|
||||
expect(host.textContent).toContain('Recommended');
|
||||
expect(host.textContent).toContain('Not recommended');
|
||||
|
|
@ -1818,7 +1846,7 @@ describe('RuntimeProviderManagementPanelView', () => {
|
|||
expect(host.textContent).not.toContain('Set OpenCode default');
|
||||
expect(
|
||||
Array.from(host.querySelectorAll('button')).some(
|
||||
(button) => button.textContent?.trim() === 'Use in team picker'
|
||||
(button) => button.textContent?.trim() === 'Save for team picker'
|
||||
)
|
||||
).toBe(false);
|
||||
expect(
|
||||
|
|
|
|||
Loading…
Reference in a new issue