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:
Jason Woltje
2026-01-29 17:57:54 -06:00
parent 95833fb4ea
commit 5dd46c85af
43 changed files with 4782 additions and 2 deletions

View File

@@ -0,0 +1,51 @@
"use client";
import type { Domain } from "@mosaic/shared";
import { DomainItem } from "./DomainItem";
interface DomainListProps {
domains: Domain[];
isLoading: boolean;
onEdit?: (domain: Domain) => void;
onDelete?: (domain: Domain) => void;
}
export function DomainList({
domains,
isLoading,
onEdit,
onDelete,
}: DomainListProps): JSX.Element {
if (isLoading) {
return (
<div className="flex justify-center items-center p-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
<span className="ml-3 text-gray-600">Loading domains...</span>
</div>
);
}
if (!domains || domains.length === 0) {
return (
<div className="text-center p-8 text-gray-500">
<p className="text-lg">No domains created yet</p>
<p className="text-sm mt-2">
Create domains to organize your tasks and projects
</p>
</div>
);
}
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{domains.map((domain) => (
<DomainItem
key={domain.id}
domain={domain}
onEdit={onEdit}
onDelete={onDelete}
/>
))}
</div>
);
}