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,80 @@
"use client";
import { useState, useEffect } from "react";
import type { Domain } from "@mosaic/shared";
import { DomainList } from "@/components/domains/DomainList";
import { fetchDomains, createDomain, updateDomain, deleteDomain } from "@/lib/api/domains";
export default function DomainsPage(): JSX.Element {
const [domains, setDomains] = useState<Domain[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadDomains();
}, []);
async function loadDomains(): Promise<void> {
try {
setIsLoading(true);
const response = await fetchDomains();
setDomains(response.data);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to load domains");
} finally {
setIsLoading(false);
}
}
function handleEdit(domain: Domain): void {
// TODO: Open edit modal/form
console.log("Edit domain:", domain);
}
async function handleDelete(domain: Domain): Promise<void> {
if (!confirm(`Are you sure you want to delete "${domain.name}"?`)) {
return;
}
try {
await deleteDomain(domain.id);
await loadDomains();
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to delete domain");
}
}
return (
<div className="max-w-6xl mx-auto p-6">
<div className="mb-6">
<h1 className="text-3xl font-bold mb-2">Domains</h1>
<p className="text-gray-600">
Organize your tasks and projects by life areas
</p>
</div>
{error && (
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded text-red-700">
{error}
</div>
)}
<div className="mb-6">
<button
className="px-4 py-2 bg-gray-900 text-white rounded hover:bg-gray-800"
onClick={() => console.log("TODO: Open create modal")}
>
Create Domain
</button>
</div>
<DomainList
domains={domains}
isLoading={isLoading}
onEdit={handleEdit}
onDelete={handleDelete}
/>
</div>
);
}