* feat(app): create agent profiles from the model chooser Restore quick model pinning by creating an agent profile directly from a model row. A trailing plus on unprofiled model rows opens the pre-filled profile form; rows that already name the provider+model show the profile glyph and open it for edit. - seed the profile form model with provider, model, and suggested name - expose useAgentProfileEditor from @/agent-profiles for the write path - hover-refactor model rows per docs/hover.md and add the trailing action - wire the composer desktop selector and compact model sheet (close-then-open) Closes #3529 * fix(app): match favourites star position for model row profile action The star sat after the selection checkmark at the far-right edge. Put the profile create/edit action in the same slot instead of before the checkmark. * fix(app): disambiguate per-model create profile action The per-model + buttons shared the accessible name "Create profile" with the empty-state row, which broke the empty-host picker test (strict mode matched five buttons). Name the action "Create profile from <model>" so it is specific and screen readers get the model context. * fix(app): keep per-model create label out of model row names Naming the action "Create profile from <model>" put the model label into a second button on the row, so regex selectors targeting a model row by name matched both. Use a generic "Create profile from this model" label that neither collides with the empty-state "Create profile" row nor with model row names.
79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
import { useCallback, useMemo, useState, type ReactElement } from "react";
|
|
import type { AgentProfile } from "@getpaseo/protocol/messages";
|
|
import { AgentProfileEditModal } from "./settings/agent-profile-edit-modal";
|
|
import { generateAgentProfileId } from "./internal/profile-id";
|
|
import type { AgentProfileSeed, AgentProfileValue } from "./internal/profile-form-model";
|
|
import { useAgentProfiles } from "./internal/use-agent-profiles";
|
|
|
|
export interface AgentProfileEditorControls {
|
|
/** Open the create form pre-filled with provider, model, and suggested name. */
|
|
openCreateFromModel: (seed: AgentProfileSeed) => void;
|
|
/** Open the edit form for one profile. */
|
|
openEdit: (profileId: string) => void;
|
|
/** Render once where a sheet can portal; it self-hides until opened. */
|
|
element: ReactElement | null;
|
|
}
|
|
|
|
type EditorRequest =
|
|
| { mode: "create"; seed?: AgentProfileSeed }
|
|
| { mode: "edit"; profile: AgentProfile };
|
|
|
|
/**
|
|
* The model chooser's create/edit surface. Owns the write path so call sites
|
|
* never see `saveProfiles` or construct profile records themselves.
|
|
*/
|
|
export function useAgentProfileEditor(serverId: string | null): AgentProfileEditorControls {
|
|
const { profiles, saveProfiles } = useAgentProfiles(serverId);
|
|
const [request, setRequest] = useState<EditorRequest | null>(null);
|
|
|
|
const openCreateFromModel = useCallback((seed: AgentProfileSeed) => {
|
|
setRequest({ mode: "create", seed });
|
|
}, []);
|
|
|
|
const openEdit = useCallback(
|
|
(profileId: string) => {
|
|
const profile = profiles?.find((entry) => entry.id === profileId);
|
|
if (!profile) {
|
|
return;
|
|
}
|
|
setRequest({ mode: "edit", profile });
|
|
},
|
|
[profiles],
|
|
);
|
|
|
|
const close = useCallback(() => setRequest(null), []);
|
|
|
|
const handleSave = useCallback(
|
|
async (value: AgentProfileValue) => {
|
|
const current = profiles ?? [];
|
|
const editing = request?.mode === "edit" ? request.profile : undefined;
|
|
const next: AgentProfile[] = editing
|
|
? current.map((entry) => (entry.id === editing.id ? { id: entry.id, ...value } : entry))
|
|
: [...current, { id: generateAgentProfileId(), ...value }];
|
|
await saveProfiles(next);
|
|
setRequest(null);
|
|
},
|
|
[profiles, request, saveProfiles],
|
|
);
|
|
|
|
const element = useMemo(
|
|
() => (
|
|
<AgentProfileEditModal
|
|
serverId={serverId ?? ""}
|
|
visible={request !== null}
|
|
mode={request?.mode ?? "create"}
|
|
{...(request?.mode === "edit" && request.profile ? { profile: request.profile } : {})}
|
|
{...(request?.mode === "create" && request.seed ? { seed: request.seed } : {})}
|
|
onClose={close}
|
|
onSave={handleSave}
|
|
/>
|
|
),
|
|
[close, handleSave, request, serverId],
|
|
);
|
|
|
|
return useMemo(
|
|
() => ({ openCreateFromModel, openEdit, element }),
|
|
[openCreateFromModel, openEdit, element],
|
|
);
|
|
}
|