"use client"; import * as React from "react"; import { GripVertical, RotateCcw, X } from "lucide-react"; import { useApp, invalidateProjects } from "@/components/app/store"; import { api } from "@/lib/client/api"; import { errorMessage } from "@/lib/client/humanize"; import { Button } from "@/components/ui/button"; import { Textarea, Field, Label } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { toast } from "@/components/ui/toast"; import { ProviderIcon } from "@/components/brand/provider-icon"; import { ModelSelector } from "@/components/chat/model-selector"; import { ParametersForm, parameterChips, type Params } from "@/components/presets/parameters-form"; import { estimateTextTokens } from "@/lib/client/tokens"; import type { PublicProject } from "@/lib/client/types"; import { formatTokens, cn } from "@/lib/utils"; interface Draft { instructions: string; preferredModelKeys: string[]; defaultSettings: Params; } const fromProject = (p: PublicProject): Draft => ({ instructions: p.instructions ?? "", preferredModelKeys: p.preferredModelKeys ?? [], defaultSettings: (p.defaultSettings ?? {}) as Params }); const same = (a: Draft, b: Draft) => a.instructions === b.instructions && JSON.stringify(a.preferredModelKeys) === JSON.stringify(b.preferredModelKeys) && JSON.stringify(a.defaultSettings) === JSON.stringify(b.defaultSettings); /** "Instructions & models" tab: system instructions, preferred models (first = default), default generation settings. */ export function ProjectSetup({ project, onSaved }: { project: PublicProject; onSaved: () => Promise | void }) { const { modelsByKey, connectedProviders } = useApp(); const [draft, setDraft] = React.useState(() => fromProject(project)); const [saving, setSaving] = React.useState(false); const base = React.useMemo(() => fromProject(project), [project]); const dirty = !same(draft, base); // Re-sync when the project changes underneath (another tab saved, SWR refresh) and nothing is being edited. const lastId = React.useRef(project.updatedAt); React.useEffect(() => { if (lastId.current !== project.updatedAt) { lastId.current = project.updatedAt; setDraft(fromProject(project)); } }, [project]); const selected = React.useMemo(() => new Set(draft.preferredModelKeys), [draft.preferredModelKeys]); const toggle = (key: string) => setDraft((d) => { const has = d.preferredModelKeys.includes(key); if (!has && d.preferredModelKeys.length >= 8) { toast.warning("Up to 8 preferred models per project"); return d; } return { ...d, preferredModelKeys: has ? d.preferredModelKeys.filter((k) => k !== key) : [...d.preferredModelKeys, key] }; }); const moveFirst = (key: string) => setDraft((d) => ({ ...d, preferredModelKeys: [key, ...d.preferredModelKeys.filter((k) => k !== key)] })); const firstModel = draft.preferredModelKeys[0] ? modelsByKey.get(draft.preferredModelKeys[0]) : null; const chips = parameterChips(draft.defaultSettings); const save = async () => { setSaving(true); try { await api(`/api/projects/${project.id}`, { method: "PATCH", json: { instructions: draft.instructions.trim() ? draft.instructions : null, preferredModelKeys: draft.preferredModelKeys, defaultSettings: draft.defaultSettings } }); await Promise.all([onSaved(), invalidateProjects()]); toast.success("Project setup saved", "New chats in this project will use it."); } catch (e) { toast.error("Could not save", errorMessage(e)); } finally { setSaving(false); } }; return (