feat(#82): implement Personality Module
- Add Personality model to Prisma schema with FormalityLevel enum - Create migration and seed with 6 default personalities - Implement CRUD API with TDD approach (97.67% coverage) * PersonalitiesService: findAll, findOne, findDefault, create, update, remove * PersonalitiesController: REST endpoints with auth guards * Comprehensive test coverage (21 passing tests) - Add Personality types to shared package - Create frontend components: * PersonalitySelector: dropdown for choosing personality * PersonalityPreview: preview personality style and system prompt * PersonalityForm: create/edit personalities with validation * Settings page: manage personalities with CRUD operations - Integrate with Ollama API: * Support personalityId in chat endpoint * Auto-inject system prompt from personality * Fall back to default personality if not specified - API client for frontend personality management All tests passing with 97.67% backend coverage (exceeds 85% requirement)
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import type { Personality } from "@mosaic/shared";
|
||||
import { fetchPersonalities } from "@/lib/api/personalities";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface PersonalitySelectorProps {
|
||||
value?: string;
|
||||
onChange?: (personalityId: string) => void;
|
||||
label?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function PersonalitySelector({
|
||||
value,
|
||||
onChange,
|
||||
label = "Select Personality",
|
||||
className,
|
||||
}: PersonalitySelectorProps): JSX.Element {
|
||||
const [personalities, setPersonalities] = useState<Personality[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadPersonalities();
|
||||
}, []);
|
||||
|
||||
async function loadPersonalities(): Promise<void> {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetchPersonalities();
|
||||
setPersonalities(response.data);
|
||||
} catch (err) {
|
||||
console.error("Failed to load personalities:", err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{label && (
|
||||
<Label htmlFor="personality-select" className="mb-2">
|
||||
{label}
|
||||
</Label>
|
||||
)}
|
||||
<Select value={value} onValueChange={onChange} disabled={isLoading}>
|
||||
<SelectTrigger id="personality-select">
|
||||
<SelectValue placeholder={isLoading ? "Loading..." : "Choose a personality"} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{personalities.map((personality) => (
|
||||
<SelectItem key={personality.id} value={personality.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{personality.name}</span>
|
||||
{personality.isDefault && (
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
Default
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user