mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-17 19:20:39 +09:00
Add company skill assignment to agent create and hire flows
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
099c37c4b4
commit
480174367d
12 changed files with 699 additions and 35 deletions
|
|
@ -25,6 +25,31 @@ function PayloadField({ label, value }: { label: string; value: unknown }) {
|
|||
);
|
||||
}
|
||||
|
||||
function SkillList({ values }: { values: unknown }) {
|
||||
if (!Array.isArray(values)) return null;
|
||||
const items = values
|
||||
.filter((value): value is string => typeof value === "string")
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
if (items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-muted-foreground w-20 sm:w-24 shrink-0 text-xs pt-0.5">Skills</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{items.map((item) => (
|
||||
<span
|
||||
key={item}
|
||||
className="rounded bg-muted px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground"
|
||||
>
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function HireAgentPayload({ payload }: { payload: Record<string, unknown> }) {
|
||||
return (
|
||||
<div className="mt-3 space-y-1.5 text-sm">
|
||||
|
|
@ -49,6 +74,7 @@ export function HireAgentPayload({ payload }: { payload: Record<string, unknown>
|
|||
</span>
|
||||
</div>
|
||||
)}
|
||||
<SkillList values={payload.desiredSkills} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import { useNavigate, useSearchParams } from "@/lib/router";
|
|||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { companySkillsApi } from "../api/companySkills";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { AGENT_ROLES } from "@paperclipai/shared";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
|
|
@ -68,6 +70,7 @@ export function NewAgent() {
|
|||
const [role, setRole] = useState("general");
|
||||
const [reportsTo, setReportsTo] = useState("");
|
||||
const [configValues, setConfigValues] = useState<CreateConfigValues>(defaultCreateValues);
|
||||
const [selectedSkillKeys, setSelectedSkillKeys] = useState<string[]>([]);
|
||||
const [roleOpen, setRoleOpen] = useState(false);
|
||||
const [reportsToOpen, setReportsToOpen] = useState(false);
|
||||
const [formError, setFormError] = useState<string | null>(null);
|
||||
|
|
@ -91,6 +94,12 @@ export function NewAgent() {
|
|||
enabled: Boolean(selectedCompanyId),
|
||||
});
|
||||
|
||||
const { data: companySkills } = useQuery({
|
||||
queryKey: queryKeys.companySkills.list(selectedCompanyId ?? ""),
|
||||
queryFn: () => companySkillsApi.list(selectedCompanyId!),
|
||||
enabled: Boolean(selectedCompanyId),
|
||||
});
|
||||
|
||||
const isFirstAgent = !agents || agents.length === 0;
|
||||
const effectiveRole = isFirstAgent ? "ceo" : role;
|
||||
|
||||
|
|
@ -174,6 +183,7 @@ export function NewAgent() {
|
|||
role: effectiveRole,
|
||||
...(title.trim() ? { title: title.trim() } : {}),
|
||||
...(reportsTo ? { reportsTo } : {}),
|
||||
...(selectedSkillKeys.length > 0 ? { desiredSkills: selectedSkillKeys } : {}),
|
||||
adapterType: configValues.adapterType,
|
||||
adapterConfig: buildAdapterConfig(),
|
||||
runtimeConfig: {
|
||||
|
|
@ -190,6 +200,16 @@ export function NewAgent() {
|
|||
}
|
||||
|
||||
const currentReportsTo = (agents ?? []).find((a) => a.id === reportsTo);
|
||||
const availableSkills = (companySkills ?? []).filter((skill) => !skill.key.startsWith("paperclipai/paperclip/"));
|
||||
|
||||
function toggleSkill(key: string, checked: boolean) {
|
||||
setSelectedSkillKeys((prev) => {
|
||||
if (checked) {
|
||||
return prev.includes(key) ? prev : [...prev, key];
|
||||
}
|
||||
return prev.filter((value) => value !== key);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl space-y-6">
|
||||
|
|
@ -311,6 +331,44 @@ export function NewAgent() {
|
|||
adapterModels={adapterModels}
|
||||
/>
|
||||
|
||||
<div className="border-t border-border px-4 py-4">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h2 className="text-sm font-medium">Company skills</h2>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Optional skills from the company library. Built-in Paperclip runtime skills are added automatically.
|
||||
</p>
|
||||
</div>
|
||||
{availableSkills.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
No optional company skills installed yet.
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{availableSkills.map((skill) => {
|
||||
const inputId = `skill-${skill.id}`;
|
||||
const checked = selectedSkillKeys.includes(skill.key);
|
||||
return (
|
||||
<div key={skill.id} className="flex items-start gap-3">
|
||||
<Checkbox
|
||||
id={inputId}
|
||||
checked={checked}
|
||||
onCheckedChange={(next) => toggleSkill(skill.key, next === true)}
|
||||
/>
|
||||
<label htmlFor={inputId} className="grid gap-1 leading-none">
|
||||
<span className="text-sm font-medium">{skill.name}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{skill.description ?? skill.key}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="border-t border-border px-4 py-3">
|
||||
{isFirstAgent && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue