1- import type { CSSProperties , ReactNode } from "react" ;
1+ import type { ReactNode } from "react" ;
22import {
33 ChatCircleDots ,
44 Checks ,
@@ -10,12 +10,75 @@ import { cn } from "@/lib/utils";
1010import { Button } from "@/components/ui/button" ;
1111import { Card , CardContent , CardFooter , CardTitle } from "@/components/ui/card" ;
1212import { Skeleton } from "@/components/ui/skeleton" ;
13+ import type { LoopPreset } from "@/lib/loopndroll" ;
14+
15+ export type ChatCardTheme = "orange" | "cyan" | "emerald" | "olive" ;
16+
17+ export function getChatCardThemeForPreset ( preset : LoopPreset | null | undefined ) : ChatCardTheme {
18+ if ( preset === "await-reply" ) {
19+ return "cyan" ;
20+ }
21+
22+ if ( preset === "completion-checks" ) {
23+ return "emerald" ;
24+ }
25+
26+ if ( preset ?. startsWith ( "max-turns-" ) ) {
27+ return "olive" ;
28+ }
29+
30+ return "orange" ;
31+ }
32+
33+ const CHAT_CARD_THEME_CLASSES : Record <
34+ ChatCardTheme ,
35+ {
36+ card : string ;
37+ marker : string ;
38+ footer : string ;
39+ footerText : string ;
40+ button : string ;
41+ }
42+ > = {
43+ orange : {
44+ card : "border-orange-900/20 bg-orange-300 text-orange-950 shadow-none" ,
45+ marker : "border-orange-300/20 bg-orange-950/80 text-orange-300" ,
46+ footer : "border-t-orange-900/15 bg-orange-400/50" ,
47+ footerText : "text-orange-950" ,
48+ button :
49+ "border-orange-950 bg-orange-950 text-orange-200 shadow-none hover:bg-orange-900 hover:text-orange-200 active:scale-[0.98] dark:border-orange-950 dark:bg-orange-950 dark:hover:bg-orange-900 dark:text-orange-200" ,
50+ } ,
51+ cyan : {
52+ card : "border-cyan-900/20 bg-cyan-300 text-cyan-950 shadow-none" ,
53+ marker : "border-cyan-300/20 bg-cyan-950/80 text-cyan-300" ,
54+ footer : "border-t-cyan-900/15 bg-cyan-400/50" ,
55+ footerText : "text-cyan-950" ,
56+ button :
57+ "border-cyan-950 bg-cyan-950 text-cyan-200 shadow-none hover:bg-cyan-900 hover:text-cyan-200 active:scale-[0.98] dark:border-cyan-950 dark:bg-cyan-950 dark:hover:bg-cyan-900 dark:text-cyan-200" ,
58+ } ,
59+ emerald : {
60+ card : "border-emerald-900/20 bg-emerald-300 text-emerald-950 shadow-none" ,
61+ marker : "border-emerald-300/20 bg-emerald-950/80 text-emerald-300" ,
62+ footer : "border-t-emerald-900/15 bg-emerald-400/50" ,
63+ footerText : "text-emerald-950" ,
64+ button :
65+ "border-emerald-950 bg-emerald-950 text-emerald-200 shadow-none hover:bg-emerald-900 hover:text-emerald-200 active:scale-[0.98] dark:border-emerald-950 dark:bg-emerald-950 dark:hover:bg-emerald-900 dark:text-emerald-200" ,
66+ } ,
67+ olive : {
68+ card : "border-olive-900/20 bg-olive-300 text-olive-950 shadow-none" ,
69+ marker : "border-olive-300/20 bg-olive-950/80 text-olive-300" ,
70+ footer : "border-t-olive-900/15 bg-olive-400/50" ,
71+ footerText : "text-olive-950" ,
72+ button :
73+ "border-olive-950 bg-olive-950 text-olive-200 shadow-none hover:bg-olive-900 hover:text-olive-200 active:scale-[0.98] dark:border-olive-950 dark:bg-olive-950 dark:hover:bg-olive-900 dark:text-olive-200" ,
74+ } ,
75+ } ;
1376
1477type ChatCardProps = {
1578 title ?: ReactNode ;
1679 marker ?: ReactNode ;
1780 markerContainerClassName ?: string ;
18- tone ?: string ;
81+ theme ?: ChatCardTheme ;
1982 isRunning ?: boolean ;
2083 actionLabel ?: string ;
2184 onAction ?: ( ) => void ;
@@ -32,7 +95,7 @@ export function ChatCard({
3295 title,
3396 marker,
3497 markerContainerClassName,
35- tone ,
98+ theme ,
3699 isRunning = false ,
37100 actionLabel = "Start" ,
38101 onAction,
@@ -45,32 +108,18 @@ export function ChatCard({
45108 footerStart,
46109} : ChatCardProps ) {
47110 const placeholder = loading || empty ;
48- const tinted = Boolean ( tone ) ;
49- const cardStyle : CSSProperties | undefined = tinted
50- ? {
51- backgroundColor : tone ,
52- color : "oklch(0.205 0 0)" ,
53- borderColor : `color-mix(in oklch, ${ tone } 78%, black)` ,
54- }
55- : undefined ;
56- const footerStyle : CSSProperties | undefined = tinted
57- ? {
58- backgroundColor : `color-mix(in oklch, ${ tone } 80%, black)` ,
59- borderTopColor : `color-mix(in oklch, ${ tone } 72%, black)` ,
60- }
61- : undefined ;
111+ const themedClasses = theme ? CHAT_CARD_THEME_CLASSES [ theme ] : null ;
62112
63113 return (
64114 < Card
65115 aria-busy = { loading || undefined }
66116 aria-hidden = { empty || undefined }
67117 className = { cn (
68118 "size-80 shrink-0 snap-start pb-0" ,
69- isRunning && "outline outline-1 -outline-offset-1 outline-green-500/50" ,
119+ themedClasses ?. card ,
70120 placeholder && "relative gap-0 overflow-hidden py-0" ,
71121 className ,
72122 ) }
73- style = { cardStyle }
74123 >
75124 < CardContent
76125 className = { cn ( "flex flex-1 items-start" , placeholder && "p-0" , contentClassName ) }
@@ -81,9 +130,9 @@ export function ChatCard({
81130 < div className = "flex flex-col items-start gap-5" >
82131 < div
83132 className = { cn (
84- "flex size-12 items-center justify-center rounded-full " ,
85- tinted
86- ? "border border-black/10 bg-black/6 text-inherit"
133+ "flex size-12 items-center justify-center rounded-md " ,
134+ themedClasses
135+ ? themedClasses . marker
87136 : "border border-white/10 bg-white/4 text-foreground" ,
88137 markerContainerClassName ,
89138 ) }
@@ -103,17 +152,19 @@ export function ChatCard({
103152 < CardFooter
104153 className = { cn (
105154 "mt-auto justify-between gap-3 border-t bg-muted/50 px-4 pb-4 [.border-t]:pt-4" ,
155+ themedClasses ?. footer ,
106156 footerClassName ,
107157 ) }
108- style = { footerStyle }
109158 >
110- < div className = "min-h-8 min-w-0" > { footerStart } </ div >
159+ < div className = { cn ( "min-h-8 min-w-0" , themedClasses ?. footerText ) } >
160+ { footerStart }
161+ </ div >
111162 < Button
112163 aria-pressed = { isRunning }
113164 onClick = { onAction }
114165 className = { cn (
115166 "w-20 gap-1.5" ,
116- tinted && "border-black/10 bg-white/35 text-black shadow-none hover:bg-white/45" ,
167+ themedClasses ?. button ,
117168 ) }
118169 size = "sm"
119170 type = "button"
0 commit comments