Files
stack/apps/web/src/components/personalities/PersonalityForm.tsx
Jason Woltje e8a9a3087a
All checks were successful
ci/woodpecker/push/orchestrator Pipeline was successful
ci/woodpecker/push/web Pipeline was successful
fix(ci): fix pipeline #366 — web @mosaic/ui build, Dockerfile find bug, event handler types
Three root causes resolved:

1. .woodpecker/web.yml: build-shared step was missing @mosaic/ui build,
   causing 10 test suite failures + 20 typecheck errors (TS2307)

2. apps/orchestrator/Dockerfile: find -o without parentheses only deleted
   last pattern's matches, leaving spec files with test fixture secrets
   that triggered 5 Trivy false positives (3 CRITICAL, 2 HIGH)

3. 9 web files had untyped event handler parameters (e) causing 49 lint
   errors and 19 typecheck errors — added React.ChangeEvent<T> types

Verification: lint 0 errors, typecheck 0 errors, tests 73/73 suites pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:50:41 -06:00

210 lines
6.9 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.SyntheticEvent<HTMLFormElement>): 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: React.ChangeEvent<HTMLInputElement>) => {
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: React.ChangeEvent<HTMLTextAreaElement>) => {
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: React.ChangeEvent<HTMLInputElement>) => {
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: React.ChangeEvent<HTMLTextAreaElement>) => {
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>
);
}