feat: add conflict dismissal functionality to team dialogs
- Introduced a conflict dismissal feature in CreateTeamDialog and LaunchTeamDialog to allow users to dismiss conflict warnings when a team is already running. - Added state management for conflict dismissal and reset logic to ensure the warning reappears when relevant conditions change. - Updated UI to include a dismiss button for better user experience in handling team conflicts.
This commit is contained in:
parent
ea4cf85e2e
commit
faf07322eb
2 changed files with 34 additions and 6 deletions
|
|
@ -28,7 +28,7 @@ import { useFileListCacheWarmer } from '@renderer/hooks/useFileListCacheWarmer';
|
||||||
import { cn } from '@renderer/lib/utils';
|
import { cn } from '@renderer/lib/utils';
|
||||||
import { normalizePath } from '@renderer/utils/pathNormalize';
|
import { normalizePath } from '@renderer/utils/pathNormalize';
|
||||||
import { getMemberColor } from '@shared/constants/memberColors';
|
import { getMemberColor } from '@shared/constants/memberColors';
|
||||||
import { AlertTriangle, CheckCircle2, Info, Loader2 } from 'lucide-react';
|
import { AlertTriangle, CheckCircle2, Info, Loader2, X } from 'lucide-react';
|
||||||
|
|
||||||
import { ExtendedContextCheckbox } from './ExtendedContextCheckbox';
|
import { ExtendedContextCheckbox } from './ExtendedContextCheckbox';
|
||||||
import { ProjectPathSelector } from './ProjectPathSelector';
|
import { ProjectPathSelector } from './ProjectPathSelector';
|
||||||
|
|
@ -225,6 +225,7 @@ export const CreateTeamDialog = ({
|
||||||
const [launchTeam, setLaunchTeam] = useState(true);
|
const [launchTeam, setLaunchTeam] = useState(true);
|
||||||
const [soloTeam, setSoloTeam] = useState(false);
|
const [soloTeam, setSoloTeam] = useState(false);
|
||||||
const [teamColor, setTeamColor] = useState('');
|
const [teamColor, setTeamColor] = useState('');
|
||||||
|
const [conflictDismissed, setConflictDismissed] = useState(false);
|
||||||
const [selectedModel, setSelectedModelRaw] = useState(() => {
|
const [selectedModel, setSelectedModelRaw] = useState(() => {
|
||||||
const stored = localStorage.getItem('team:lastSelectedModel') ?? '';
|
const stored = localStorage.getItem('team:lastSelectedModel') ?? '';
|
||||||
return stored === '__default__' ? '' : stored;
|
return stored === '__default__' ? '' : stored;
|
||||||
|
|
@ -250,6 +251,7 @@ export const CreateTeamDialog = ({
|
||||||
setPrepareState('idle');
|
setPrepareState('idle');
|
||||||
setPrepareMessage(null);
|
setPrepareMessage(null);
|
||||||
setPrepareWarnings([]);
|
setPrepareWarnings([]);
|
||||||
|
setConflictDismissed(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetFormState = (): void => {
|
const resetFormState = (): void => {
|
||||||
|
|
@ -492,6 +494,11 @@ export const CreateTeamDialog = ({
|
||||||
return activeTeams.find((t) => normalizePath(t.projectPath) === norm) ?? null;
|
return activeTeams.find((t) => normalizePath(t.projectPath) === norm) ?? null;
|
||||||
}, [activeTeams, effectiveCwd]);
|
}, [activeTeams, effectiveCwd]);
|
||||||
|
|
||||||
|
// Reset dismiss when conflict target changes
|
||||||
|
useEffect(() => {
|
||||||
|
setConflictDismissed(false);
|
||||||
|
}, [conflictingTeam?.teamName, effectiveCwd]);
|
||||||
|
|
||||||
const handleSubmit = (): void => {
|
const handleSubmit = (): void => {
|
||||||
if (existingTeamNames.includes(sanitizedTeamName)) {
|
if (existingTeamNames.includes(sanitizedTeamName)) {
|
||||||
setFieldErrors({ teamName: 'Team name already exists' });
|
setFieldErrors({ teamName: 'Team name already exists' });
|
||||||
|
|
@ -565,11 +572,11 @@ export const CreateTeamDialog = ({
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{conflictingTeam ? (
|
{conflictingTeam && !conflictDismissed ? (
|
||||||
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-xs">
|
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-xs">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-amber-400" />
|
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-amber-400" />
|
||||||
<div className="min-w-0 space-y-1">
|
<div className="min-w-0 flex-1 space-y-1">
|
||||||
<p className="font-medium text-amber-300">
|
<p className="font-medium text-amber-300">
|
||||||
Team “{conflictingTeam.displayName}” is already running in this
|
Team “{conflictingTeam.displayName}” is already running in this
|
||||||
project
|
project
|
||||||
|
|
@ -579,6 +586,13 @@ export const CreateTeamDialog = ({
|
||||||
same files. Consider using a different directory or a git worktree for isolation.
|
same files. Consider using a different directory or a git worktree for isolation.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="shrink-0 rounded p-0.5 text-amber-400/60 transition-colors hover:text-amber-300"
|
||||||
|
onClick={() => setConflictDismissed(true)}
|
||||||
|
>
|
||||||
|
<X className="size-3.5" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import { useStore } from '@renderer/store';
|
||||||
import { formatAgentRole } from '@renderer/utils/formatAgentRole';
|
import { formatAgentRole } from '@renderer/utils/formatAgentRole';
|
||||||
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
|
import { buildMemberColorMap } from '@renderer/utils/memberHelpers';
|
||||||
import { normalizePath } from '@renderer/utils/pathNormalize';
|
import { normalizePath } from '@renderer/utils/pathNormalize';
|
||||||
import { AlertTriangle, CheckCircle2, Loader2, RotateCcw } from 'lucide-react';
|
import { AlertTriangle, CheckCircle2, Loader2, RotateCcw, X } from 'lucide-react';
|
||||||
|
|
||||||
import { ProjectPathSelector } from './ProjectPathSelector';
|
import { ProjectPathSelector } from './ProjectPathSelector';
|
||||||
import { computeEffectiveTeamModel, TeamModelSelector } from './TeamModelSelector';
|
import { computeEffectiveTeamModel, TeamModelSelector } from './TeamModelSelector';
|
||||||
|
|
@ -79,6 +79,7 @@ export const LaunchTeamDialog = ({
|
||||||
() => localStorage.getItem('team:lastExtendedContext') === 'true'
|
() => localStorage.getItem('team:lastExtendedContext') === 'true'
|
||||||
);
|
);
|
||||||
const [clearContext, setClearContext] = useState(false);
|
const [clearContext, setClearContext] = useState(false);
|
||||||
|
const [conflictDismissed, setConflictDismissed] = useState(false);
|
||||||
|
|
||||||
const setSelectedModel = (value: string): void => {
|
const setSelectedModel = (value: string): void => {
|
||||||
setSelectedModelRaw(value);
|
setSelectedModelRaw(value);
|
||||||
|
|
@ -100,6 +101,7 @@ export const LaunchTeamDialog = ({
|
||||||
setSelectedProjectPath('');
|
setSelectedProjectPath('');
|
||||||
setCustomCwd('');
|
setCustomCwd('');
|
||||||
setClearContext(false);
|
setClearContext(false);
|
||||||
|
setConflictDismissed(false);
|
||||||
chipDraft.clearChipDraft();
|
chipDraft.clearChipDraft();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -245,6 +247,11 @@ export const LaunchTeamDialog = ({
|
||||||
);
|
);
|
||||||
}, [activeTeams, effectiveCwd, teamName]);
|
}, [activeTeams, effectiveCwd, teamName]);
|
||||||
|
|
||||||
|
// Reset dismiss when conflict target changes (different path or different conflicting team)
|
||||||
|
useEffect(() => {
|
||||||
|
setConflictDismissed(false);
|
||||||
|
}, [conflictingTeam?.teamName, effectiveCwd]);
|
||||||
|
|
||||||
const colorMap = useMemo(() => buildMemberColorMap(members), [members]);
|
const colorMap = useMemo(() => buildMemberColorMap(members), [members]);
|
||||||
const mentionSuggestions = useMemo<MentionSuggestion[]>(
|
const mentionSuggestions = useMemo<MentionSuggestion[]>(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -305,11 +312,11 @@ export const LaunchTeamDialog = ({
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{conflictingTeam ? (
|
{conflictingTeam && !conflictDismissed ? (
|
||||||
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-xs">
|
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-xs">
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-amber-400" />
|
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-amber-400" />
|
||||||
<div className="min-w-0 space-y-1">
|
<div className="min-w-0 flex-1 space-y-1">
|
||||||
<p className="font-medium text-amber-300">
|
<p className="font-medium text-amber-300">
|
||||||
Team “{conflictingTeam.displayName}” is already running in this
|
Team “{conflictingTeam.displayName}” is already running in this
|
||||||
project
|
project
|
||||||
|
|
@ -319,6 +326,13 @@ export const LaunchTeamDialog = ({
|
||||||
same files. Consider using a different directory or a git worktree for isolation.
|
same files. Consider using a different directory or a git worktree for isolation.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="shrink-0 rounded p-0.5 text-amber-400/60 transition-colors hover:text-amber-300"
|
||||||
|
onClick={() => setConflictDismissed(true)}
|
||||||
|
>
|
||||||
|
<X className="size-3.5" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue