196 lines
6.5 KiB
TypeScript
196 lines
6.5 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import type { Personality, FormalityLevel } from "@mosaic/shared";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
|
export interface PersonalityFormData {
|
|
name: string;
|
|
description?: string;
|
|
tone: string;
|
|
formalityLevel: FormalityLevel;
|
|
systemPromptTemplate: string;
|
|
isDefault?: boolean;
|
|
isActive?: boolean;
|
|
}
|
|
|
|
interface PersonalityFormProps {
|
|
personality?: Personality;
|
|
onSubmit: (data: PersonalityFormData) => Promise<void>;
|
|
onCancel?: () => void;
|
|
}
|
|
|
|
const FORMALITY_OPTIONS = [
|
|
{ value: "VERY_CASUAL", label: "Very Casual" },
|
|
{ value: "CASUAL", label: "Casual" },
|
|
{ value: "NEUTRAL", label: "Neutral" },
|
|
{ value: "FORMAL", label: "Formal" },
|
|
{ value: "VERY_FORMAL", label: "Very Formal" },
|
|
];
|
|
|
|
export function PersonalityForm({ personality, onSubmit, onCancel }: PersonalityFormProps): React.ReactElement {
|
|
const [formData, setFormData] = useState<PersonalityFormData>({
|
|
name: personality?.name || "",
|
|
description: personality?.description || "",
|
|
tone: personality?.tone || "",
|
|
formalityLevel: (personality?.formalityLevel ?? "NEUTRAL") as FormalityLevel,
|
|
systemPromptTemplate: personality?.systemPromptTemplate || "",
|
|
isDefault: personality?.isDefault || false,
|
|
isActive: personality?.isActive ?? true,
|
|
});
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
async function handleSubmit(e: React.FormEvent): Promise<void> {
|
|
e.preventDefault();
|
|
setIsSubmitting(true);
|
|
try {
|
|
await onSubmit(formData);
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit}>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{personality ? "Edit Personality" : "Create New Personality"}</CardTitle>
|
|
<CardDescription>
|
|
Customize how the AI assistant communicates and responds
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* Name */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="name">Name *</Label>
|
|
<Input
|
|
id="name"
|
|
value={formData.name}
|
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
|
placeholder="e.g., Professional, Casual, Friendly"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
{/* Description */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="description">Description</Label>
|
|
<Textarea
|
|
id="description"
|
|
value={formData.description}
|
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
|
placeholder="Brief description of this personality style"
|
|
rows={2}
|
|
/>
|
|
</div>
|
|
|
|
{/* Tone */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="tone">Tone *</Label>
|
|
<Input
|
|
id="tone"
|
|
value={formData.tone}
|
|
onChange={(e) => setFormData({ ...formData, tone: e.target.value })}
|
|
placeholder="e.g., professional, friendly, enthusiastic"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
{/* Formality Level */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="formality">Formality Level *</Label>
|
|
<Select
|
|
value={formData.formalityLevel}
|
|
onValueChange={(value) =>
|
|
setFormData({ ...formData, formalityLevel: value as FormalityLevel })
|
|
}
|
|
>
|
|
<SelectTrigger id="formality">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{FORMALITY_OPTIONS.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* System Prompt Template */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="systemPrompt">System Prompt Template *</Label>
|
|
<Textarea
|
|
id="systemPrompt"
|
|
value={formData.systemPromptTemplate}
|
|
onChange={(e) =>
|
|
setFormData({ ...formData, systemPromptTemplate: e.target.value })
|
|
}
|
|
placeholder="You are a helpful AI assistant..."
|
|
rows={6}
|
|
required
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
This template guides the AI's communication style and behavior
|
|
</p>
|
|
</div>
|
|
|
|
{/* Switches */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label htmlFor="isDefault">Set as Default</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
Use this personality by default for new conversations
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
id="isDefault"
|
|
checked={formData.isDefault ?? false}
|
|
onCheckedChange={(checked) => setFormData({ ...formData, isDefault: checked })}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5">
|
|
<Label htmlFor="isActive">Active</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
Make this personality available for selection
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
id="isActive"
|
|
checked={formData.isActive ?? true}
|
|
onCheckedChange={(checked) => setFormData({ ...formData, isActive: checked })}
|
|
/>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
<div className="flex justify-end gap-2 pt-4">
|
|
{onCancel && (
|
|
<Button type="button" variant="outline" onClick={onCancel}>
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
<Button type="submit" disabled={isSubmitting}>
|
|
{isSubmitting ? "Saving..." : personality ? "Update" : "Create"}
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</form>
|
|
);
|
|
}
|