TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1"use client";2import * as React from "react";3import Link from "next/link";4import { KeyRound, Code2, PenLine, Search, FileUp, Columns3, EyeOff } from "lucide-react";5import type { PolyModel } from "@/lib/client/types";6import { useApp } from "@/components/app/store";7import { LogoMark } from "@/components/brand/logo";8import { Button } from "@/components/ui/button";9import { CapabilityBadges } from "./model-badges";10import { Onboarding } from "@/components/app/onboarding";11import { cn } from "@/lib/utils";1213interface QuickAction {14 key: string;15 icon: React.ReactNode;16 title: string;17 hint: string;18 /** Text inserted into the composer (Write / Code / Research). */19 text?: string;20}2122const QUICK: QuickAction[] = [23 { key: "write", icon: <PenLine className="size-4" />, title: "Write", hint: "Emails, posts, docs", text: "Help me write a concise, friendly " },24 { key: "code", icon: <Code2 className="size-4" />, title: "Code", hint: "Review, debug, generate", text: "Review the following code for bugs, edge cases and readability, and suggest concrete fixes.\n\n```\n\n```" },25 { key: "research", icon: <Search className="size-4" />, title: "Research", hint: "Sources, comparisons", text: "Research the current state of " },26 { key: "file", icon: <FileUp className="size-4" />, title: "Analyze a file", hint: "PDF, CSV, image, code" },27 { key: "compare", icon: <Columns3 className="size-4" />, title: "Compare models", hint: "Same prompt, 2–4 models" },28];2930export function ChatEmptyState({ model, onPick, onAnalyzeFile, onCompare, temporary, projectName }: { model?: PolyModel; onPick: (text: string) => void; onAnalyzeFile?: () => void; onCompare?: () => void; temporary?: boolean; projectName?: string | null }) {31 const { connectedProviders, loadingModels, user, connections } = useApp();32 const noProviders = !loadingModels && connectedProviders.size === 0;33 return (34 <div className="mx-auto flex h-full w-full max-w-3xl flex-col items-center justify-center px-4 py-8 text-center sm:py-10">35 {!user.onboardingCompletedAt && connections.length === 0 ? <Onboarding /> : null}36 <LogoMark size={44} className="mb-4 rounded-xl shadow-md" />37 <h1 className="text-balance text-[22px] font-semibold tracking-tight sm:text-2xl">What do you want to work on?</h1>38 <p className="mt-1.5 max-w-md text-balance text-[14px] text-fg-muted">39 {temporary ? (40 <span className="inline-flex items-center gap-1.5">41 <EyeOff className="size-3.5" /> Temporary chat — not stored in history.42 </span>43 ) : projectName ? (44 <>45 In project <span className="font-medium text-fg">{projectName}</span>46 {model ? (47 <>48 {" "}49 · <span className="font-medium text-fg">{model.displayName}</span>50 </>51 ) : null}52 </>53 ) : model ? (54 <>55 Chatting with <span className="font-medium text-fg">{model.displayName}</span>. <span className="hidden sm:inline">Switch anytime with ⌘/.</span>56 </>57 ) : (58 "One interface. Every model. Bring your own keys."59 )}60 </p>61 {model ? <CapabilityBadges model={model} className="mt-3 justify-center" /> : null}62 {noProviders ? (63 <div className="mt-6 flex flex-col items-center gap-3 rounded-xl border border-border bg-bg-elevated p-5">64 <KeyRound className="size-5 text-accent" />65 <p className="text-sm font-medium">Connect your first provider</p>66 <p className="max-w-sm text-[13px] text-fg-muted">Add a key from OpenAI, Anthropic, Gemini, xAI, Mistral, DeepSeek, Kimi, OpenRouter or Cerebras. Keys are encrypted at rest and only decrypted server-side right before a request.</p>67 <Button asChild variant="accent" size="sm">68 <Link href="/app/settings/providers">Connect a provider</Link>69 </Button>70 </div>71 ) : (72 <div className="mt-7 grid w-full grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-5">73 {QUICK.map((q) => {74 const onClick = q.key === "file" ? onAnalyzeFile : q.key === "compare" ? onCompare : () => onPick(q.text ?? "");75 return (76 <button key={q.key} type="button" onClick={onClick} disabled={!onClick} className={cn("flex min-h-[84px] flex-col items-start gap-2 rounded-xl border border-border bg-bg-elevated p-3 text-left transition-colors hover:border-border-strong hover:bg-bg-subtle disabled:opacity-50", q.key === "compare" && "col-span-2 sm:col-span-1")}>77 <span className="flex size-8 items-center justify-center rounded-lg bg-accent-soft text-accent">{q.icon}</span>78 <span className="min-w-0">79 <span className="block text-[13.5px] font-medium">{q.title}</span>80 <span className="block text-[12px] text-fg-muted">{q.hint}</span>81 </span>82 </button>83 );84 })}85 </div>86 )}87 </div>88 );89}90