spb/chat-spboucher-ai
Public
1"use client";2import { useState, type FormEvent } from "react";3import { useRouter } from "next/navigation";4import { mutate } from "swr";5import { FolderKanban } from "lucide-react";6import { api } from "@/lib/client/fetcher";7import { useModels } from "@/lib/client/use-models";8import { Button, Input, Textarea, toast } from "@/components/ui";9import { ColorPicker, PROJECT_COLORS } from "@/components/projects/project-shared";1011export default function NewProjectPage() {12 const router = useRouter(); const { chat } = useModels();13 const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [instructions, setInstructions] = useState(""); const [color, setColor] = useState(PROJECT_COLORS[0]); const [defaultModel, setDefaultModel] = useState(""); const [busy, setBusy] = useState(false);14 async function submit(e: FormEvent) {15 e.preventDefault(); setBusy(true);16 try {17 const r = await api<{ project: { id: string } }>("/api/projects", { method: "POST", json: { name: name.trim(), description: description.trim() || undefined, instructions: instructions.trim() || undefined, color, defaultModel: defaultModel || null } });18 void mutate("/api/projects"); toast("Projet créé", "ok"); router.push(`/projects/${r.project.id}`);19 } catch (err) { toast((err as Error).message, "error"); setBusy(false); }20 }21 return (22 <div className="min-h-0 flex-1 overflow-y-auto">23 <form onSubmit={submit} className="mx-auto w-full max-w-2xl px-4 py-6 md:px-8">24 <h1 className="mb-1 flex items-center gap-2 text-xl font-semibold tracking-tight"><FolderKanban className="h-5 w-5 text-accent" /> Nouveau projet</h1>25 <p className="mb-6 text-[13px] text-muted">Un projet regroupe des conversations, des fichiers, des instructions, un modèle par défaut et une mémoire dédiée.</p>26 <div className="card space-y-4 p-5">27 <div><label className="mb-1 block text-xs font-medium text-fg-2">Nom</label><Input autoFocus required maxLength={120} value={name} onChange={e => setName(e.target.value)} placeholder="Ex. : Recherche UQO, Maclustr, Immobilier…" /></div>28 <div><label className="mb-1 block text-xs font-medium text-fg-2">Description</label><Input maxLength={2000} value={description} onChange={e => setDescription(e.target.value)} placeholder="Une ligne pour te rappeler l'objectif" /></div>29 <div><label className="mb-1 block text-xs font-medium text-fg-2">Couleur</label><ColorPicker value={color} onChange={setColor} /></div>30 <div><label className="mb-1 block text-xs font-medium text-fg-2">Modèle par défaut</label>31 <select value={defaultModel} onChange={e => setDefaultModel(e.target.value)} className="input"><option value="">— Suivre le choix du composeur —</option>{chat.map(m => <option key={m.id} value={m.id}>{m.displayName} ({m.provider})</option>)}</select></div>32 <div><label className="mb-1 block text-xs font-medium text-fg-2">Instructions du projet</label><Textarea value={instructions} onChange={e => setInstructions(e.target.value)} maxLength={20000} placeholder="Contexte, ton, contraintes… Ces instructions affinent le comportement du modèle sans jamais contourner les règles de sécurité." className="min-h-32 text-[14px]" /></div>33 </div>34 <div className="mt-4 flex justify-end gap-2"><Button type="button" variant="ghost" onClick={() => router.back()}>Annuler</Button><Button type="submit" variant="primary" loading={busy} disabled={!name.trim()}>Créer le projet</Button></div>35 </form>36 </div>37 );38}39