refactor: update site header and chat components for improved user experience
All checks were successful
Deploy / deploy (push) Successful in 12m16s
All checks were successful
Deploy / deploy (push) Successful in 12m16s
Replaced the static logo in the SiteHeader with an animated component for enhanced visual feedback. Updated the PaketSatinAl component to include a dynamic link based on user context. Enhanced the ListeUretici and ListemGovde components with improved layout and messaging features, including the addition of a new initial preference comment display in the SohbetClient. Adjusted styling for consistency across components and improved loading states with skeleton screens.
This commit is contained in:
@@ -7,6 +7,7 @@ import Image from "next/image";
|
||||
import { ArrowUp, Coins, Info, Lock, Sparkles, Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AnimasyonluMark } from "@/components/animasyonlu-mark";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ChatContainerContent,
|
||||
@@ -53,20 +54,53 @@ export interface DanismanOzeti {
|
||||
kilitli?: boolean;
|
||||
}
|
||||
|
||||
/** Danışman mesajlarının avatarı — KolayTercih markası. */
|
||||
function DanismanAvatar() {
|
||||
/** Liste kurulunca sohbetin açılışında gösterilen 1. tercih yorumu.
|
||||
* Metin ACIK_SATIR maskesinin her zaman açık bıraktığı ilk satırdan gelir,
|
||||
* yani paketsiz kullanıcıda da gerçek Yapay Zeka yorumudur. */
|
||||
export interface IlkTercihYorumu {
|
||||
program: string;
|
||||
universite: string;
|
||||
metin: string;
|
||||
}
|
||||
|
||||
/** Danışman panellerinin ortak başlığı: animasyonlu logo + site header'daki
|
||||
* KolayTercih wordmark deseninde "DanışmanlaKonuş" (ilk kelime ince, ikinci
|
||||
* kalın, boşluksuz). İşaret ortak AnimasyonluMark bileşeni — header
|
||||
* logosuyla aynı nabız animasyonu, burada sürekli atar. */
|
||||
export function DanismanBaslik() {
|
||||
return (
|
||||
// gap yok denecek kadar az (0.5): SVG viewBox'ının kendi iç boşluğu
|
||||
// zaten nefes payı bırakıyor.
|
||||
<h2 className="flex min-w-0 flex-1 items-center gap-0.5 font-bricolage text-base tracking-tighter">
|
||||
<AnimasyonluMark varyant="nabiz" renk="turuncu" className="size-5" />
|
||||
<span className="truncate">
|
||||
<span className="font-light">Danışmanla</span>
|
||||
<span className="font-semibold">Konuş</span>
|
||||
</span>
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
|
||||
/** Danışman mesajlarının avatarı — KolayTercih markası. `canli` iken (cevap
|
||||
* beklenirken/yazılırken) statik görsel yerine başlıktaki nabız animasyonu
|
||||
* oynar: danışmanın "düşündüğü" avatardan okunur. */
|
||||
export function DanismanAvatar({ canli = false }: { canli?: boolean }) {
|
||||
return (
|
||||
<span
|
||||
className="mt-0.5 flex size-8 shrink-0 items-center justify-center overflow-hidden rounded-full border border-slate-200 bg-white"
|
||||
aria-hidden
|
||||
>
|
||||
<Image
|
||||
src="/kolay-tercih-mark.svg"
|
||||
alt=""
|
||||
width={32}
|
||||
height={32}
|
||||
className="size-5"
|
||||
/>
|
||||
{canli ? (
|
||||
<AnimasyonluMark varyant="nabiz" className="size-5" />
|
||||
) : (
|
||||
<Image
|
||||
src="/kolay-tercih-mark.svg"
|
||||
alt=""
|
||||
width={32}
|
||||
height={32}
|
||||
className="size-5"
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -87,6 +121,7 @@ export function SohbetClient({
|
||||
initialMesajlar,
|
||||
className,
|
||||
ozet,
|
||||
ilkYorum,
|
||||
bildikleri,
|
||||
}: {
|
||||
baslangicKredi: number;
|
||||
@@ -96,6 +131,10 @@ export function SohbetClient({
|
||||
className?: string;
|
||||
/** Raporun genel değerlendirmesi; sohbetin açılış mesajı olarak gösterilir */
|
||||
ozet?: DanismanOzeti;
|
||||
/** 1. tercihin Yapay Zeka yorumu; sohbet boşken (taze kurulmuş liste)
|
||||
* özetin altında açılış balonu olarak durur — üretim panelindeki "ilk
|
||||
* tercihinin yorumunu burada okuyacaksın" sözünün karşılığı. */
|
||||
ilkYorum?: IlkTercihYorumu;
|
||||
/** Danışmanın prompt'unda gerçekten yer alan bağlam maddeleri; başlıktaki
|
||||
* "Danışmanın bildikleri" ipucunda listelenir. Boşsa ipucu çizilmez. */
|
||||
bildikleri?: string[];
|
||||
@@ -253,62 +292,70 @@ export function SohbetClient({
|
||||
aria-label="Danışman sohbeti"
|
||||
className={cn(
|
||||
"flex flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white",
|
||||
className ?? "h-[calc(100dvh-24rem)] min-h-[340px]",
|
||||
// 18rem: sticky ofset (top-28 = 7rem) + geniş alt boşluk — eski 24rem
|
||||
// sohbeti sıkıştırıyordu, 10-14rem denemeleri fazla yüksek geldi;
|
||||
// panel görünür ama sayfaya baskın değil.
|
||||
className ?? "h-[calc(100dvh-18rem)] min-h-[380px]",
|
||||
)}
|
||||
>
|
||||
{/* Başlık solda, aksiyonlar sağda tek grup: dar panelde de sığar.
|
||||
Temizle butonu ikon-only — metinli hâli başlıkla yarışıp taşıyordu. */}
|
||||
<div className="flex items-center gap-2 border-b border-slate-100 px-4 py-3">
|
||||
{bildikleri?.length ? (
|
||||
<TooltipProvider delayDuration={120}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Danışmanın bildikleri"
|
||||
className="inline-flex size-7 cursor-help items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-800"
|
||||
<DanismanBaslik />
|
||||
<div className="flex shrink-0 items-center gap-1.5">
|
||||
{bildikleri?.length ? (
|
||||
<TooltipProvider delayDuration={120}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Danışmanın bildikleri"
|
||||
className="inline-flex size-7 cursor-help items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 transition-colors hover:bg-slate-100 hover:text-slate-800"
|
||||
>
|
||||
<Info className="size-3.5" aria-hidden />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="bottom"
|
||||
sideOffset={6}
|
||||
className="max-w-xs flex-col items-start gap-1.5 p-3 text-left leading-relaxed"
|
||||
>
|
||||
<Info className="size-3.5" aria-hidden />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="bottom"
|
||||
sideOffset={6}
|
||||
className="max-w-xs flex-col items-start gap-1.5 p-3 text-left leading-relaxed"
|
||||
>
|
||||
<p className="font-semibold">
|
||||
Danışmanın bildikleri — cevaplar bu bağlamla üretilir:
|
||||
</p>
|
||||
<ul className="flex list-disc flex-col gap-1 pl-4">
|
||||
{bildikleri.map((b) => (
|
||||
<li key={b}>{b}</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-background/70">
|
||||
Adın, e-postan ve netlerin danışmanla paylaşılmaz.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : null}
|
||||
{mesajlar.length > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTemizleAcik(true)}
|
||||
disabled={bekliyor || temizleniyor}
|
||||
className="inline-flex cursor-pointer items-center gap-1.5 rounded-full border border-slate-200 bg-white px-2.5 py-1 text-xs font-semibold text-slate-600 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
title="Sohbet geçmişini sil"
|
||||
<p className="font-semibold">
|
||||
Danışmanın bildikleri — cevaplar bu bağlamla üretilir:
|
||||
</p>
|
||||
<ul className="flex list-disc flex-col gap-1 pl-4">
|
||||
{bildikleri.map((b) => (
|
||||
<li key={b}>{b}</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-background/70">
|
||||
Adın, e-postan ve netlerin danışmanla paylaşılmaz.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : null}
|
||||
{mesajlar.length > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTemizleAcik(true)}
|
||||
disabled={bekliyor || temizleniyor}
|
||||
aria-label="Sohbeti temizle"
|
||||
className="inline-flex size-7 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
title="Sohbet geçmişini sil"
|
||||
>
|
||||
<Trash2 className="size-3.5" aria-hidden />
|
||||
</button>
|
||||
) : null}
|
||||
<Link
|
||||
href="/paket"
|
||||
className="inline-flex items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-semibold text-amber-700 transition-colors hover:bg-amber-100"
|
||||
title="Kredilerin — yüklemek için tıkla"
|
||||
>
|
||||
<Trash2 className="size-3.5" aria-hidden />
|
||||
Sohbeti temizle
|
||||
</button>
|
||||
) : null}
|
||||
<Link
|
||||
href="/paket"
|
||||
className="ml-auto inline-flex items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-semibold text-amber-700 transition-colors hover:bg-amber-100"
|
||||
title="Kredilerin — yüklemek için tıkla"
|
||||
>
|
||||
<Coins className="size-3.5" aria-hidden />
|
||||
{kredi} kredi
|
||||
</Link>
|
||||
<Coins className="size-3.5" aria-hidden />
|
||||
{kredi} kredi
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog open={temizleAcik} onOpenChange={setTemizleAcik}>
|
||||
@@ -363,6 +410,19 @@ export function SohbetClient({
|
||||
</div>
|
||||
</Message>
|
||||
) : null}
|
||||
{ilkYorum && mesajlar.length === 0 ? (
|
||||
<Message className="justify-start">
|
||||
<DanismanAvatar />
|
||||
<div className="flex max-w-[85%] flex-col gap-2">
|
||||
<p className="text-xs font-semibold text-slate-500">
|
||||
1. tercihin: {ilkYorum.program} — {ilkYorum.universite}
|
||||
</p>
|
||||
<MessageContent className="bg-slate-100 text-slate-800">
|
||||
{ilkYorum.metin}
|
||||
</MessageContent>
|
||||
</div>
|
||||
</Message>
|
||||
) : null}
|
||||
{mesajlar.length === 0 ? (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -424,7 +484,7 @@ export function SohbetClient({
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
mesajlar.map((m) =>
|
||||
mesajlar.map((m, i) =>
|
||||
m.role === "user" ? (
|
||||
<Message key={m.id} className="justify-end">
|
||||
<MessageContent className="max-w-[85%] bg-primary text-white">
|
||||
@@ -433,7 +493,11 @@ export function SohbetClient({
|
||||
</Message>
|
||||
) : (
|
||||
<Message key={m.id} className="justify-start">
|
||||
<DanismanAvatar />
|
||||
{/* Cevap beklenirken/stream sürerken (son mesaj) avatar
|
||||
nabız animasyonuna geçer. */}
|
||||
<DanismanAvatar
|
||||
canli={bekliyor && i === mesajlar.length - 1}
|
||||
/>
|
||||
{m.content ? (
|
||||
<MessageContent
|
||||
markdown
|
||||
|
||||
Reference in New Issue
Block a user