Files
paseo/packages/app/src/agent-profiles/agent-profile-editor.tsx
T
Mohamed BoudraandGitHub e9373c026b feat(app): create agent profiles from the model chooser (#3533)
* 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.
2026-08-19 07:46:47 +02:00

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],
);
}