- 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)
157 lines
4.1 KiB
TypeScript
157 lines
4.1 KiB
TypeScript
import {
|
|
Injectable,
|
|
NotFoundException,
|
|
ConflictException,
|
|
Logger,
|
|
} from "@nestjs/common";
|
|
import { PrismaService } from "../prisma/prisma.service";
|
|
import { CreatePersonalityDto, UpdatePersonalityDto } from "./dto";
|
|
import { Personality } from "./entities/personality.entity";
|
|
|
|
@Injectable()
|
|
export class PersonalitiesService {
|
|
private readonly logger = new Logger(PersonalitiesService.name);
|
|
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
/**
|
|
* Find all personalities for a workspace
|
|
*/
|
|
async findAll(workspaceId: string, isActive: boolean = true): Promise<Personality[]> {
|
|
return this.prisma.personality.findMany({
|
|
where: { workspaceId, isActive },
|
|
orderBy: [{ isDefault: "desc" }, { name: "asc" }],
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Find a specific personality by ID
|
|
*/
|
|
async findOne(workspaceId: string, id: string): Promise<Personality> {
|
|
const personality = await this.prisma.personality.findUnique({
|
|
where: { id, workspaceId },
|
|
});
|
|
|
|
if (!personality) {
|
|
throw new NotFoundException(`Personality with ID ${id} not found`);
|
|
}
|
|
|
|
return personality;
|
|
}
|
|
|
|
/**
|
|
* Find the default personality for a workspace
|
|
*/
|
|
async findDefault(workspaceId: string): Promise<Personality> {
|
|
const personality = await this.prisma.personality.findFirst({
|
|
where: { workspaceId, isDefault: true, isActive: true },
|
|
});
|
|
|
|
if (!personality) {
|
|
throw new NotFoundException(`No default personality found for workspace ${workspaceId}`);
|
|
}
|
|
|
|
return personality;
|
|
}
|
|
|
|
/**
|
|
* Create a new personality
|
|
*/
|
|
async create(workspaceId: string, dto: CreatePersonalityDto): Promise<Personality> {
|
|
// Check for duplicate name
|
|
const existing = await this.prisma.personality.findFirst({
|
|
where: { workspaceId, name: dto.name },
|
|
});
|
|
|
|
if (existing) {
|
|
throw new ConflictException(`Personality with name "${dto.name}" already exists`);
|
|
}
|
|
|
|
// If creating a default personality, unset other defaults
|
|
if (dto.isDefault) {
|
|
await this.unsetOtherDefaults(workspaceId);
|
|
}
|
|
|
|
const personality = await this.prisma.personality.create({
|
|
data: {
|
|
workspaceId,
|
|
...dto,
|
|
},
|
|
});
|
|
|
|
this.logger.log(`Created personality ${personality.id} for workspace ${workspaceId}`);
|
|
return personality;
|
|
}
|
|
|
|
/**
|
|
* Update an existing personality
|
|
*/
|
|
async update(
|
|
workspaceId: string,
|
|
id: string,
|
|
dto: UpdatePersonalityDto,
|
|
): Promise<Personality> {
|
|
// Check existence
|
|
await this.findOne(workspaceId, id);
|
|
|
|
// Check for duplicate name if updating name
|
|
if (dto.name) {
|
|
const existing = await this.prisma.personality.findFirst({
|
|
where: { workspaceId, name: dto.name, id: { not: id } },
|
|
});
|
|
|
|
if (existing) {
|
|
throw new ConflictException(`Personality with name "${dto.name}" already exists`);
|
|
}
|
|
}
|
|
|
|
// If setting as default, unset other defaults
|
|
if (dto.isDefault === true) {
|
|
await this.unsetOtherDefaults(workspaceId, id);
|
|
}
|
|
|
|
const personality = await this.prisma.personality.update({
|
|
where: { id },
|
|
data: dto,
|
|
});
|
|
|
|
this.logger.log(`Updated personality ${id} for workspace ${workspaceId}`);
|
|
return personality;
|
|
}
|
|
|
|
/**
|
|
* Delete a personality
|
|
*/
|
|
async remove(workspaceId: string, id: string): Promise<Personality> {
|
|
// Check existence
|
|
await this.findOne(workspaceId, id);
|
|
|
|
const personality = await this.prisma.personality.delete({
|
|
where: { id },
|
|
});
|
|
|
|
this.logger.log(`Deleted personality ${id} from workspace ${workspaceId}`);
|
|
return personality;
|
|
}
|
|
|
|
/**
|
|
* Unset the default flag on all other personalities in the workspace
|
|
*/
|
|
private async unsetOtherDefaults(workspaceId: string, excludeId?: string): Promise<void> {
|
|
const currentDefault = await this.prisma.personality.findFirst({
|
|
where: {
|
|
workspaceId,
|
|
isDefault: true,
|
|
...(excludeId && { id: { not: excludeId } }),
|
|
},
|
|
});
|
|
|
|
if (currentDefault) {
|
|
await this.prisma.personality.update({
|
|
where: { id: currentDefault.id },
|
|
data: { isDefault: false },
|
|
});
|
|
}
|
|
}
|
|
}
|