* Switch models from the Command Center Add a model switcher to the Command-K Command Center. Typing surfaces a flat, filterable list of "Model › Provider › Name" breadcrumb rows with provider icons: - Running agent: its own provider's models (a live agent can't change provider); selecting calls setAgentModel. - New draft tab: every available provider's models in one flat list; selecting sets provider + model on the draft via a focused-draft controller published to a global store (the draft form state is local to the composer subtree and otherwise unreachable from the global Command Center). Models only appear once the user starts typing, so the default palette view is unchanged. Reuses useProvidersSnapshot and the existing setAgentModel RPC — no protocol changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update packages/app/src/components/command-center.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update packages/app/src/hooks/use-command-center.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * refactor(app): make Command Center extensible Let focused features register stable actions while the palette owns search, selection, and a single virtualized result projection. * fix(app): save model preference after agent switch Persist the shared model choice only after the daemon confirms the live agent switched successfully. --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
import type {
|
|
CommandCenterContribution,
|
|
CommandCenterContributionSnapshot,
|
|
CommandCenterRegistration,
|
|
CommandCenterRegistrationOwner,
|
|
} from "./contributions";
|
|
|
|
export interface CommandCenterRegistry {
|
|
getSnapshot(): CommandCenterContributionSnapshot;
|
|
subscribe(listener: () => void): () => void;
|
|
replace(registration: CommandCenterRegistration): void;
|
|
remove(owner: CommandCenterRegistrationOwner): void;
|
|
}
|
|
|
|
interface ActiveRegistration {
|
|
owner: CommandCenterRegistrationOwner;
|
|
contributions: readonly CommandCenterContribution[];
|
|
}
|
|
|
|
const EMPTY_SNAPSHOT: CommandCenterContributionSnapshot = { contributions: [] };
|
|
|
|
function contributionId(sourceId: string, id: string): string {
|
|
return `${sourceId}:${id}`;
|
|
}
|
|
|
|
function compareContributions(
|
|
left: CommandCenterContribution,
|
|
right: CommandCenterContribution,
|
|
): number {
|
|
if (left.groupRank !== right.groupRank) return left.groupRank - right.groupRank;
|
|
const groupDelta = left.group.localeCompare(right.group);
|
|
if (groupDelta !== 0) return groupDelta;
|
|
if (left.rank !== right.rank) return left.rank - right.rank;
|
|
return left.id.localeCompare(right.id);
|
|
}
|
|
|
|
function sameContributions(
|
|
left: readonly CommandCenterContribution[],
|
|
right: readonly CommandCenterContribution[],
|
|
): boolean {
|
|
return left.length === right.length && left.every((item, index) => item === right[index]);
|
|
}
|
|
|
|
export function createCommandCenterRegistry(): CommandCenterRegistry {
|
|
const registrations = new Map<string, ActiveRegistration>();
|
|
const listeners = new Set<() => void>();
|
|
let snapshot = EMPTY_SNAPSHOT;
|
|
|
|
function publish(): void {
|
|
const contributions: CommandCenterContribution[] = [];
|
|
const ids = new Set<string>();
|
|
|
|
for (const registration of registrations.values()) {
|
|
for (const contribution of registration.contributions) {
|
|
const id = contributionId(registration.owner.sourceId, contribution.id);
|
|
if (ids.has(id)) {
|
|
throw new Error(`Duplicate Command Center contribution id: ${id}`);
|
|
}
|
|
ids.add(id);
|
|
contributions.push({ ...contribution, id });
|
|
}
|
|
}
|
|
contributions.sort(compareContributions);
|
|
|
|
if (sameContributions(snapshot.contributions, contributions)) return;
|
|
snapshot = contributions.length === 0 ? EMPTY_SNAPSHOT : { contributions };
|
|
for (const listener of listeners) listener();
|
|
}
|
|
|
|
return {
|
|
getSnapshot: () => snapshot,
|
|
subscribe(listener) {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
},
|
|
replace(registration) {
|
|
const current = registrations.get(registration.owner.sourceId);
|
|
if (
|
|
current?.owner.token === registration.owner.token &&
|
|
sameContributions(current.contributions, registration.contributions)
|
|
) {
|
|
return;
|
|
}
|
|
const ids = new Set<string>();
|
|
for (const contribution of registration.contributions) {
|
|
const id = contributionId(registration.owner.sourceId, contribution.id);
|
|
if (ids.has(id)) throw new Error(`Duplicate Command Center contribution id: ${id}`);
|
|
ids.add(id);
|
|
}
|
|
registrations.set(registration.owner.sourceId, registration);
|
|
publish();
|
|
},
|
|
remove(owner) {
|
|
const current = registrations.get(owner.sourceId);
|
|
if (current?.owner.token !== owner.token) return;
|
|
registrations.delete(owner.sourceId);
|
|
publish();
|
|
},
|
|
};
|
|
}
|