SPB Git
3.5 KB · 39 lines tsx
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
Files · main
Menu

Theme