({
skill,
profileId,
organizationId,
onEdit,
}: {
skill: ProfileSkill;
profileId: string;
organizationId: string | undefined;
onEdit: () => void;
})
| 199 | if (!taken.has(base)) return base; |
| 200 | for (let i = 2; i < 1000; i += 1) { |
| 201 | const candidate = `${base}-${i}`; |
| 202 | if (!taken.has(candidate)) return candidate; |
| 203 | } |
| 204 | return `${base}-${Date.now()}`; |
| 205 | } |
| 206 | |
| 207 | function SkillRow({ |
| 208 | skill, |
| 209 | profileId, |
| 210 | organizationId, |
| 211 | onEdit, |
| 212 | }: { |
| 213 | skill: ProfileSkill; |
| 214 | profileId: string; |
| 215 | organizationId: string | undefined; |
| 216 | onEdit: () => void; |
| 217 | }) { |
| 218 | const { deleteSkill } = useProfileMutations({ organizationId }); |
| 219 | const [deleting, setDeleting] = useState(false); |
| 220 | |
| 221 | const fileCount = Object.keys(skill.files ?? {}).length; |
| 222 | const companionBytes = Object.values(skill.files ?? {}).reduce((sum, c) => sum + c.length, 0); |
| 223 | const totalBytes = skill.rawMarkdown.length + companionBytes; |
| 224 | const sizeKb = (totalBytes / 1024).toFixed(1); |
| 225 | |
| 226 | return ( |
| 227 | <div className="hover:bg-accent/50 rounded-lg border p-3 transition-colors"> |
| 228 | <div className="flex items-start justify-between gap-2"> |
| 229 | <div className="min-w-0 flex-1"> |
| 230 | <div className="flex items-center gap-2"> |
| 231 | <Sparkles className="text-muted-foreground h-4 w-4" /> |
| 232 | <code className="bg-muted rounded px-2 py-0.5 font-mono text-sm">{skill.name}</code> |
| 233 | <span className="text-muted-foreground text-xs uppercase tracking-wide"> |
| 234 | {skill.sourceType} |
| 235 | </span> |
| 236 | <span className="text-muted-foreground text-xs">{sizeKb} KB</span> |
| 237 | {fileCount > 0 && ( |
| 238 | <span className="text-muted-foreground text-xs"> |
| 239 | +{fileCount} file{fileCount === 1 ? '' : 's'} |
| 240 | </span> |
| 241 | )} |
| 242 | </div> |
| 243 | {skill.description && ( |
| 244 | <p className="text-muted-foreground mt-0.5 line-clamp-2 text-xs">{skill.description}</p> |
| 245 | )} |
| 246 | </div> |
| 247 | <div className="flex shrink-0 items-center gap-2"> |
| 248 | <Button |
| 249 | variant="ghost" |
| 250 | size="sm" |
| 251 | className="h-7" |
| 252 | onClick={onEdit} |
| 253 | disabled={deleting} |
| 254 | aria-label="Edit skill" |
| 255 | > |
| 256 | <Pencil className="h-3.5 w-3.5" /> |
| 257 | </Button> |
| 258 | <InlineDeleteConfirmation |
nothing calls this directly
no test coverage detected