mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-22 03:02:35 +00:00
Clarify assistant and patrol settings ownership
This commit is contained in:
parent
1175c1d2be
commit
2d9c30dbed
12 changed files with 132 additions and 57 deletions
|
|
@ -311,7 +311,13 @@ work extends shared components instead of creating new local variants.
|
|||
controls inside `frontend-modern/src/components/Settings/AIRuntimeControlsSection.tsx`
|
||||
must likewise describe discovery as workload discovery that supplies
|
||||
concrete service context to Pulse Assistant and Patrol, not as a generic
|
||||
AI context feature.
|
||||
AI context feature. Assistant-only controls inside the shared shell, such
|
||||
as execution permissions and session maintenance, must stay explicitly
|
||||
labeled as Pulse Assistant controls, while Patrol schedule and autonomy
|
||||
continue to live on Patrol-owned surfaces rather than drifting back into
|
||||
the shared settings shell. Shared/default model choices may remain on the
|
||||
combined shell only when Assistant and Patrol overrides are presented as
|
||||
explicit per-surface overrides instead of a generic advanced AI bucket.
|
||||
|
||||
## Forbidden Paths
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, For, Show } from 'solid-js';
|
||||
import type { AISettingsState } from '@/components/Settings/useAISettingsState';
|
||||
import {
|
||||
AI_SETTINGS_ASSISTANT_SESSIONS_TITLE,
|
||||
getAIChatSessionsEmptyState,
|
||||
getAIChatSessionsLoadingState,
|
||||
} from '@/utils/aiSettingsPresentation';
|
||||
|
|
@ -34,7 +35,9 @@ export const AIChatMaintenanceSection: Component<AIChatMaintenanceSectionProps>
|
|||
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-base-content">Chat Session Maintenance</span>
|
||||
<span class="text-sm font-medium text-base-content">
|
||||
{AI_SETTINGS_ASSISTANT_SESSIONS_TITLE}
|
||||
</span>
|
||||
</div>
|
||||
<svg
|
||||
class={`w-4 h-4 transition-transform ${state.showChatMaintenance() ? 'rotate-180' : ''}`}
|
||||
|
|
@ -48,8 +51,8 @@ export const AIChatMaintenanceSection: Component<AIChatMaintenanceSectionProps>
|
|||
<Show when={state.showChatMaintenance()}>
|
||||
<div class="px-3 py-3 bg-surface border-t border-border space-y-3">
|
||||
<p class="text-xs text-muted">
|
||||
Use this panel to summarize, inspect, or revert a specific chat session. It does not
|
||||
change your default Pulse Assistant settings.
|
||||
Summarize, inspect, or revert a specific Pulse Assistant session. It does not change
|
||||
Patrol settings or your shared provider and model defaults.
|
||||
</p>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs font-medium text-muted">Session</label>
|
||||
|
|
@ -101,7 +104,7 @@ export const AIChatMaintenanceSection: Component<AIChatMaintenanceSectionProps>
|
|||
disabled={!state.selectedSessionId() || state.sessionActionLoading() !== null}
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-3 py-2 text-sm font-medium rounded border border-border bg-surface text-base-content hover:bg-surface-hover disabled:opacity-50"
|
||||
>
|
||||
{state.sessionActionLoading() === 'summarize' ? 'Summarizing...' : 'Summarize context'}
|
||||
{state.sessionActionLoading() === 'summarize' ? 'Summarizing...' : 'Summarize session'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -109,7 +112,7 @@ export const AIChatMaintenanceSection: Component<AIChatMaintenanceSectionProps>
|
|||
disabled={!state.selectedSessionId() || state.sessionActionLoading() !== null}
|
||||
class="w-full sm:w-auto min-h-10 sm:min-h-9 px-3 py-2 text-sm font-medium rounded border border-border bg-surface text-base-content hover:bg-surface-hover disabled:opacity-50"
|
||||
>
|
||||
{state.sessionActionLoading() === 'diff' ? 'Loading...' : 'View file changes'}
|
||||
{state.sessionActionLoading() === 'diff' ? 'Loading...' : 'View session changes'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from '@/components/Settings/aiSettingsModel';
|
||||
import type { AISettingsState } from '@/components/Settings/useAISettingsState';
|
||||
import { formField, labelClass, controlClass } from '@/components/shared/Form';
|
||||
import { AI_SETTINGS_MODEL_OVERRIDES_TITLE } from '@/utils/aiSettingsPresentation';
|
||||
import { getAIProviderDisplayName, getProviderFromModelId } from '@/utils/aiProviderPresentation';
|
||||
|
||||
interface AIModelSelectionSectionProps {
|
||||
|
|
@ -22,7 +23,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
|||
<div class={formField}>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<label class={labelClass()}>
|
||||
Default Model
|
||||
Shared Default Model
|
||||
{state.modelsLoading() && <span class="ml-2 text-xs text-slate-500">(loading...)</span>}
|
||||
</label>
|
||||
<button
|
||||
|
|
@ -157,6 +158,9 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
|||
to be configured. Add an API key below or select a different model.
|
||||
</p>
|
||||
</Show>
|
||||
<p class="text-[11px] text-muted mt-1">
|
||||
Used by both Pulse Assistant and Patrol unless you set a section-specific override below.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="border border-border rounded-md overflow-hidden">
|
||||
|
|
@ -174,7 +178,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
|||
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-base-content">Advanced Model Selection</span>
|
||||
<span class="text-sm font-medium text-base-content">{AI_SETTINGS_MODEL_OVERRIDES_TITLE}</span>
|
||||
<Show when={state.form.chatModel || state.form.patrolModel}>
|
||||
<span class="px-1.5 py-0.5 text-[10px] font-semibold bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||
Customized
|
||||
|
|
@ -193,12 +197,13 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
|||
<Show when={state.showAdvancedModels()}>
|
||||
<div class="px-3 py-3 bg-surface border-t border-border space-y-3">
|
||||
<p class="text-xs text-muted">
|
||||
Override the default model for specific tasks. Leave empty to use the default.
|
||||
Override the shared default for Pulse Assistant or Patrol. Leave empty to use the
|
||||
shared default model.
|
||||
</p>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-muted mb-0.5">Chat Model (Interactive)</label>
|
||||
<label class="block text-xs font-medium text-muted mb-0.5">Pulse Assistant Model</label>
|
||||
<p class="text-[11px] text-muted mb-1">
|
||||
Used for chat and fix execution — a more capable model is recommended.
|
||||
Used for live chat and approved fix execution — a more capable model is recommended.
|
||||
</p>
|
||||
<Show
|
||||
when={state.availableModels().length > 0}
|
||||
|
|
@ -207,7 +212,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
|||
type="text"
|
||||
value={state.form.chatModel}
|
||||
onInput={(e) => state.setForm('chatModel', e.currentTarget.value)}
|
||||
placeholder="Use default model"
|
||||
placeholder="Use shared default model"
|
||||
class={controlClass()}
|
||||
disabled={state.saving()}
|
||||
/>
|
||||
|
|
@ -220,7 +225,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
|||
disabled={state.saving()}
|
||||
>
|
||||
<option value="">
|
||||
Use default ({state.form.model?.split(':').pop() || 'not set'})
|
||||
Use shared default ({state.form.model?.split(':').pop() || 'not set'})
|
||||
</option>
|
||||
<For each={groupedModels()}>
|
||||
{([provider, models]) => (
|
||||
|
|
@ -237,9 +242,12 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
|||
</Show>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-muted mb-0.5">Patrol Model (Background)</label>
|
||||
<label class="block text-xs font-medium text-muted mb-0.5">
|
||||
Patrol Verification Model
|
||||
</label>
|
||||
<p class="text-[11px] text-muted mb-1">
|
||||
Runs frequently for detection — a smaller, cheaper model keeps costs low.
|
||||
Used for recurring verification and finding generation — a smaller, cheaper model
|
||||
keeps costs low.
|
||||
</p>
|
||||
<Show
|
||||
when={state.availableModels().length > 0}
|
||||
|
|
@ -248,7 +256,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
|||
type="text"
|
||||
value={state.form.patrolModel}
|
||||
onInput={(e) => state.setForm('patrolModel', e.currentTarget.value)}
|
||||
placeholder="Use default model"
|
||||
placeholder="Use shared default model"
|
||||
class={controlClass()}
|
||||
disabled={state.saving()}
|
||||
/>
|
||||
|
|
@ -261,7 +269,7 @@ export const AIModelSelectionSection: Component<AIModelSelectionSectionProps> =
|
|||
disabled={state.saving()}
|
||||
>
|
||||
<option value="">
|
||||
Use default ({state.form.model?.split(':').pop() || 'not set'})
|
||||
Use shared default ({state.form.model?.split(':').pop() || 'not set'})
|
||||
</option>
|
||||
<For each={groupedModels()}>
|
||||
{([provider, models]) => (
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
getAIControlLevelPanelClass,
|
||||
} from '@/utils/aiControlLevelPresentation';
|
||||
import {
|
||||
AI_SETTINGS_ASSISTANT_PERMISSIONS_TITLE,
|
||||
getAISettingsWorkloadDiscoveryHelpContent,
|
||||
getAISettingsWorkloadDiscoverySummary,
|
||||
} from '@/utils/aiSettingsPresentation';
|
||||
|
|
@ -198,7 +199,9 @@ export const AIRuntimeControlsSection: Component<AIRuntimeControlsSectionProps>
|
|||
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm font-medium text-base-content">Pulse Permission Level</span>
|
||||
<span class="text-sm font-medium text-base-content">
|
||||
{AI_SETTINGS_ASSISTANT_PERMISSIONS_TITLE}
|
||||
</span>
|
||||
<Show when={state.form.controlLevel !== 'read_only'}>
|
||||
<span class={`px-1.5 py-0.5 text-[10px] font-medium rounded ${getAIControlLevelBadgeClass(state.form.controlLevel)}`}>
|
||||
{state.form.controlLevel}
|
||||
|
|
@ -207,7 +210,7 @@ export const AIRuntimeControlsSection: Component<AIRuntimeControlsSectionProps>
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="text-xs font-medium text-muted w-28 flex-shrink-0">Permission</label>
|
||||
<label class="text-xs font-medium text-muted w-28 flex-shrink-0">Control mode</label>
|
||||
<select
|
||||
value={state.form.controlLevel}
|
||||
onChange={(e) => state.setForm('controlLevel', e.currentTarget.value as AIControlLevel)}
|
||||
|
|
@ -263,7 +266,9 @@ export const AIRuntimeControlsSection: Component<AIRuntimeControlsSectionProps>
|
|||
|
||||
<Show when={state.form.controlLevel !== 'read_only'}>
|
||||
<div class="flex items-start gap-3 pt-2 border-t border-blue-200 dark:border-blue-700">
|
||||
<label class="text-xs font-medium text-muted w-28 flex-shrink-0 pt-1">Protected</label>
|
||||
<label class="text-xs font-medium text-muted w-28 flex-shrink-0 pt-1">
|
||||
Protected guests
|
||||
</label>
|
||||
<div class="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -274,7 +279,8 @@ export const AIRuntimeControlsSection: Component<AIRuntimeControlsSectionProps>
|
|||
disabled={state.saving()}
|
||||
/>
|
||||
<p class="text-[10px] text-muted mt-1">
|
||||
Comma-separated VMIDs or names that Pulse Assistant cannot control
|
||||
Comma-separated VMIDs or names that Pulse Assistant cannot control, even when
|
||||
command execution is enabled.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { For, Show, type Accessor, type Component, type Setter } from 'solid-js'
|
|||
import { Dialog } from '@/components/shared/Dialog';
|
||||
import { SelectionCardGroup } from '@/components/shared/SelectionCardGroup';
|
||||
import { getAISessionDiffStatusPresentation } from '@/utils/aiSessionDiffPresentation';
|
||||
import { getAISettingsSetupDialogPresentation } from '@/utils/aiSettingsPresentation';
|
||||
import { RELAY_ONBOARDING_TRIAL_STARTING_LABEL } from '@/utils/relayPresentation';
|
||||
import type { FileChange } from '@/api/aiChat';
|
||||
import type { AIProvider } from '@/types/ai';
|
||||
|
|
@ -36,26 +37,7 @@ export interface AISettingsDialogsProps {
|
|||
|
||||
export const AISettingsDialogs: Component<AISettingsDialogsProps> = (props) => {
|
||||
const setupProviderConfig = () => getAIProviderConfig(props.setupProvider());
|
||||
const setupTitle = () => {
|
||||
switch (props.setupMode()) {
|
||||
case 'activation-or-provider':
|
||||
return 'Activate quickstart or connect a provider';
|
||||
case 'provider-required':
|
||||
return 'Connect a provider to continue';
|
||||
default:
|
||||
return 'Set Up Pulse Assistant';
|
||||
}
|
||||
};
|
||||
const setupDescription = () => {
|
||||
switch (props.setupMode()) {
|
||||
case 'activation-or-provider':
|
||||
return 'Start a trial to unlock Patrol quickstart, or connect your own provider below.';
|
||||
case 'provider-required':
|
||||
return 'Patrol quickstart is not currently available. Connect a provider to continue.';
|
||||
default:
|
||||
return 'Choose a provider to get started';
|
||||
}
|
||||
};
|
||||
const setupPresentation = () => getAISettingsSetupDialogPresentation(props.setupMode());
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -120,12 +102,12 @@ export const AISettingsDialogs: Component<AISettingsDialogsProps> = (props) => {
|
|||
onClose={props.handleCloseSetupModal}
|
||||
panelClass="max-w-md"
|
||||
closeOnBackdrop={false}
|
||||
ariaLabel="Set up Pulse Assistant"
|
||||
ariaLabel={setupPresentation().ariaLabel}
|
||||
>
|
||||
<div class="w-full overflow-hidden">
|
||||
<div class="bg-blue-600 px-6 py-4">
|
||||
<h3 class="text-lg font-semibold text-white">{setupTitle()}</h3>
|
||||
<p class="text-blue-100 text-sm mt-1">{setupDescription()}</p>
|
||||
<h3 class="text-lg font-semibold text-white">{setupPresentation().title}</h3>
|
||||
<p class="text-blue-100 text-sm mt-1">{setupPresentation().description}</p>
|
||||
</div>
|
||||
|
||||
<div class="p-6 space-y-4">
|
||||
|
|
@ -233,7 +215,7 @@ export const AISettingsDialogs: Component<AISettingsDialogsProps> = (props) => {
|
|||
{props.setupSaving() && (
|
||||
<span class="h-4 w-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
)}
|
||||
Enable Pulse Assistant
|
||||
{setupPresentation().submitLabel}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -477,8 +477,10 @@ describe('AISettings quickstart enablement flow', () => {
|
|||
expect(updateSettingsMock).toHaveBeenCalledWith({ enabled: true });
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Choose a provider to get started')).not.toBeInTheDocument();
|
||||
expect(notificationSuccessMock).toHaveBeenCalledWith('Pulse Assistant enabled');
|
||||
expect(
|
||||
screen.queryByText('Connect a provider to power Pulse Assistant and Patrol.'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(notificationSuccessMock).toHaveBeenCalledWith('Assistant & Patrol enabled');
|
||||
});
|
||||
|
||||
it('shows activation-aware setup guidance instead of generic provider setup when quickstart is blocked', async () => {
|
||||
|
|
|
|||
|
|
@ -1115,7 +1115,8 @@ describe('Settings architecture guardrails', () => {
|
|||
'@/components/Settings/AIProviderConfigurationSection',
|
||||
);
|
||||
expect(aiModelSelectionSectionSource).toContain('@/components/Settings/aiSettingsModel');
|
||||
expect(aiModelSelectionSectionSource).toContain('Advanced Model Selection');
|
||||
expect(aiModelSelectionSectionSource).toContain('@/utils/aiSettingsPresentation');
|
||||
expect(aiModelSelectionSectionSource).toContain('AI_SETTINGS_MODEL_OVERRIDES_TITLE');
|
||||
expect(aiRuntimeControlsSectionSource).toContain('@/components/shared/UpgradeLink');
|
||||
expect(aiRuntimeControlsSectionSource).toContain('@/utils/upgradePresentation');
|
||||
expect(aiRuntimeControlsSectionSource).toContain('UPGRADE_ACTION_LABEL');
|
||||
|
|
@ -1123,17 +1124,20 @@ describe('Settings architecture guardrails', () => {
|
|||
expect(aiRuntimeControlsSectionSource).toContain('Workload Discovery');
|
||||
expect(aiRuntimeControlsSectionSource).toContain('getAISettingsWorkloadDiscoveryHelpContent');
|
||||
expect(aiRuntimeControlsSectionSource).toContain('getAISettingsWorkloadDiscoverySummary');
|
||||
expect(aiRuntimeControlsSectionSource).toContain('Pulse Permission Level');
|
||||
expect(aiRuntimeControlsSectionSource).toContain('@/utils/aiSettingsPresentation');
|
||||
expect(aiRuntimeControlsSectionSource).toContain('AI_SETTINGS_ASSISTANT_PERMISSIONS_TITLE');
|
||||
expect(aiRuntimeControlsSectionSource).toContain('destination={state.upgradeAutofixDestination()}');
|
||||
expect(aiRuntimeControlsSectionSource).not.toContain('href={state.upgradeAutofixDestination().href}');
|
||||
expect(aiRuntimeControlsSectionSource).not.toContain('window.open(state.upgradeAutofixDestination().href');
|
||||
expect(aiRuntimeControlsSectionSource).not.toContain('>Upgrade to Pro<');
|
||||
expect(aiRuntimeControlsSectionSource).not.toContain('>Start free trial<');
|
||||
expect(aiChatMaintenanceSectionSource).toContain('Chat Session Maintenance');
|
||||
expect(aiChatMaintenanceSectionSource).toContain('@/utils/aiSettingsPresentation');
|
||||
expect(aiChatMaintenanceSectionSource).toContain('AI_SETTINGS_ASSISTANT_SESSIONS_TITLE');
|
||||
expect(aiSettingsStatusAndActionsSource).toContain('Save changes');
|
||||
expect(aiSettingsStatusAndActionsSource).toContain('Test Connection');
|
||||
expect(aiProviderConfigurationSectionSource).toContain('@/components/Settings/aiSettingsModel');
|
||||
expect(aiSettingsDialogsSource).toContain('@/components/Settings/aiSettingsModel');
|
||||
expect(aiSettingsDialogsSource).toContain('getAISettingsSetupDialogPresentation');
|
||||
expect(aiSettingsModelSource).toContain('export const AI_PROVIDER_CONFIGS');
|
||||
expect(aiSettingsModelSource).toContain('export const AI_SETUP_PROVIDER_OPTIONS');
|
||||
expect(aiSettingsStateSource).toContain('export const useAISettingsState =');
|
||||
|
|
|
|||
|
|
@ -489,7 +489,7 @@ export const useAISettingsState = () => {
|
|||
syncModelCatalogForSettings(updated);
|
||||
void runProviderPreflight(updated);
|
||||
handleCloseSetupModal();
|
||||
notificationStore.success('Pulse Assistant enabled! You can customize settings below.');
|
||||
notificationStore.success('Assistant & Patrol enabled! You can customize settings below.');
|
||||
} catch (error) {
|
||||
logger.error('[AISettings] Setup failed:', error);
|
||||
notificationStore.error(error instanceof Error ? error.message : 'Setup failed');
|
||||
|
|
@ -811,7 +811,7 @@ export const useAISettingsState = () => {
|
|||
setSettings(updated);
|
||||
syncModelCatalogForSettings(updated);
|
||||
void runProviderPreflight(updated);
|
||||
notificationStore.success(newValue ? 'Pulse Assistant enabled' : 'Pulse Assistant disabled');
|
||||
notificationStore.success(newValue ? 'Assistant & Patrol enabled' : 'Assistant & Patrol disabled');
|
||||
} catch (error) {
|
||||
setForm('enabled', !newValue);
|
||||
logger.error('[AISettings] Failed to toggle AI:', error);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
AI_SETTINGS_ASSISTANT_PERMISSIONS_TITLE,
|
||||
AI_SETTINGS_ASSISTANT_SESSIONS_TITLE,
|
||||
AI_SETTINGS_MODEL_OVERRIDES_TITLE,
|
||||
AI_SETTINGS_PANEL_DESCRIPTION,
|
||||
AI_SETTINGS_PANEL_TITLE,
|
||||
getAICredentialsClearErrorMessage,
|
||||
|
|
@ -17,6 +20,7 @@ import {
|
|||
getAISettingsReadinessPresentation,
|
||||
getAISettingsRetryLabel,
|
||||
getAISettingsSaveErrorMessage,
|
||||
getAISettingsSetupDialogPresentation,
|
||||
getAISettingsToggleErrorMessage,
|
||||
getAISettingsWorkloadDiscoveryHelpContent,
|
||||
getAISettingsWorkloadDiscoverySummary,
|
||||
|
|
@ -28,6 +32,9 @@ describe('aiSettingsPresentation', () => {
|
|||
expect(AI_SETTINGS_PANEL_DESCRIPTION).toBe(
|
||||
'Configure providers and models for Pulse Assistant and Patrol.',
|
||||
);
|
||||
expect(AI_SETTINGS_MODEL_OVERRIDES_TITLE).toBe('Assistant & Patrol Model Overrides');
|
||||
expect(AI_SETTINGS_ASSISTANT_SESSIONS_TITLE).toBe('Pulse Assistant Sessions');
|
||||
expect(AI_SETTINGS_ASSISTANT_PERMISSIONS_TITLE).toBe('Pulse Assistant Permissions');
|
||||
expect(getAISettingsWorkloadDiscoveryHelpContent()).toEqual({
|
||||
title: 'What is workload discovery?',
|
||||
description:
|
||||
|
|
@ -36,6 +43,19 @@ describe('aiSettingsPresentation', () => {
|
|||
expect(getAISettingsWorkloadDiscoverySummary()).toEqual({
|
||||
text: 'Workload discovery gives Pulse Assistant and Patrol concrete service context, so chat responses and verification findings can reference real services and commands instead of generic advice.',
|
||||
});
|
||||
expect(getAISettingsSetupDialogPresentation('provider')).toEqual({
|
||||
ariaLabel: 'Set up Assistant and Patrol',
|
||||
title: 'Set Up Assistant & Patrol',
|
||||
description: 'Connect a provider to power Pulse Assistant and Patrol.',
|
||||
submitLabel: 'Enable Assistant & Patrol',
|
||||
});
|
||||
expect(getAISettingsSetupDialogPresentation('activation-or-provider')).toEqual({
|
||||
ariaLabel: 'Activate quickstart or connect a provider',
|
||||
title: 'Activate quickstart or connect a provider',
|
||||
description:
|
||||
'Start a trial to unlock Patrol quickstart, or connect your own provider for Pulse Assistant and Patrol.',
|
||||
submitLabel: 'Enable Assistant & Patrol',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the canonical provider-backed ready presentation', () => {
|
||||
|
|
|
|||
|
|
@ -24,12 +24,24 @@ const AI_OAUTH_ERROR_MESSAGES: Record<string, string> = {
|
|||
export const AI_SETTINGS_PANEL_TITLE = 'Assistant & Patrol';
|
||||
export const AI_SETTINGS_PANEL_DESCRIPTION =
|
||||
'Configure providers and models for Pulse Assistant and Patrol.';
|
||||
export const AI_SETTINGS_MODEL_OVERRIDES_TITLE = 'Assistant & Patrol Model Overrides';
|
||||
export const AI_SETTINGS_ASSISTANT_SESSIONS_TITLE = 'Pulse Assistant Sessions';
|
||||
export const AI_SETTINGS_ASSISTANT_PERMISSIONS_TITLE = 'Pulse Assistant Permissions';
|
||||
export const AI_SETTINGS_LOAD_MODELS_ERROR = 'Unable to load models.';
|
||||
export const AI_SETTINGS_LOAD_CHAT_SESSIONS_ERROR = 'Unable to load chat sessions.';
|
||||
export const AI_SETTINGS_LOAD_FAILURE_MESSAGE =
|
||||
'Unable to load Assistant & Patrol settings. Your configuration could not be retrieved.';
|
||||
export const AI_SETTINGS_LOAD_RETRY_LABEL = 'Retry';
|
||||
|
||||
export type AISettingsSetupMode = 'provider' | 'activation-or-provider' | 'provider-required';
|
||||
|
||||
export interface AISettingsSetupDialogPresentation {
|
||||
ariaLabel: string;
|
||||
description: string;
|
||||
submitLabel: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export function getAIProviderTestResultTextClass(success: boolean): string {
|
||||
return success ? 'text-green-600' : 'text-red-600';
|
||||
}
|
||||
|
|
@ -48,6 +60,36 @@ export function getAISettingsWorkloadDiscoverySummary() {
|
|||
} as const;
|
||||
}
|
||||
|
||||
export function getAISettingsSetupDialogPresentation(
|
||||
mode: AISettingsSetupMode,
|
||||
): AISettingsSetupDialogPresentation {
|
||||
switch (mode) {
|
||||
case 'activation-or-provider':
|
||||
return {
|
||||
ariaLabel: 'Activate quickstart or connect a provider',
|
||||
title: 'Activate quickstart or connect a provider',
|
||||
description:
|
||||
'Start a trial to unlock Patrol quickstart, or connect your own provider for Pulse Assistant and Patrol.',
|
||||
submitLabel: 'Enable Assistant & Patrol',
|
||||
};
|
||||
case 'provider-required':
|
||||
return {
|
||||
ariaLabel: 'Connect a provider to continue',
|
||||
title: 'Connect a provider to continue',
|
||||
description:
|
||||
'Patrol quickstart is not currently available. Connect a provider for Pulse Assistant and Patrol.',
|
||||
submitLabel: 'Enable Assistant & Patrol',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
ariaLabel: 'Set up Assistant and Patrol',
|
||||
title: 'Set Up Assistant & Patrol',
|
||||
description: 'Connect a provider to power Pulse Assistant and Patrol.',
|
||||
submitLabel: 'Enable Assistant & Patrol',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getAISettingsReadinessPresentation(
|
||||
input: AISettingsReadinessInput,
|
||||
): AISettingsReadinessPresentation {
|
||||
|
|
|
|||
|
|
@ -658,6 +658,8 @@ test.describe("Quickstart cross-surface browser contract", () => {
|
|||
|
||||
await expect.poll(() => surface.updateRequests.length).toBe(1);
|
||||
expect(surface.updateRequests[0]).toMatchObject({ enabled: true });
|
||||
await expect(page.getByText("Choose a provider to get started")).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByText("Connect a provider to power Pulse Assistant and Patrol."),
|
||||
).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -113,12 +113,12 @@ test.describe("Assistant & Patrol settings provider setup", () => {
|
|||
).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: /enable assistant and patrol/i }).click();
|
||||
const setupDialog = page.getByRole("dialog", { name: "Set up Pulse Assistant" });
|
||||
await expect(setupDialog.getByText("Set Up Pulse Assistant")).toBeVisible();
|
||||
const setupDialog = page.getByRole("dialog", { name: "Set up Assistant and Patrol" });
|
||||
await expect(setupDialog.getByText("Set Up Assistant & Patrol")).toBeVisible();
|
||||
|
||||
await setupDialog.getByRole("button", { name: /OpenRouter/i }).click();
|
||||
await setupDialog.getByPlaceholder("sk-or-...").fill("sk-or-runtime-selected");
|
||||
await setupDialog.getByRole("button", { name: "Enable Pulse Assistant" }).click();
|
||||
await setupDialog.getByRole("button", { name: "Enable Assistant & Patrol" }).click();
|
||||
|
||||
await expect.poll(() => updateRequests.length).toBe(1);
|
||||
expect(updateRequests[0]).toEqual({
|
||||
|
|
@ -127,7 +127,7 @@ test.describe("Assistant & Patrol settings provider setup", () => {
|
|||
});
|
||||
expect(updateRequests[0]).not.toHaveProperty("model");
|
||||
|
||||
await expect(page.getByText("Advanced Model Selection")).toBeVisible();
|
||||
await expect(page.getByText("Assistant & Patrol Model Overrides")).toBeVisible();
|
||||
const workloadDiscoveryToggle = page.getByRole("button", { name: /workload discovery/i });
|
||||
await expect(workloadDiscoveryToggle).toBeVisible();
|
||||
await workloadDiscoveryToggle.click();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue