1414namespace PilotAIAssistantControl {
1515
1616 public class ChatItem {
17+ private const string UserSender = "You" ;
18+ private const string AiSender = "AI Assistant" ;
19+ private const string SystemSender = "System" ;
20+
21+ #if ! WPF
22+ // Application.Current.RequestedTheme is set once at startup and does NOT update
23+ // when the system theme changes at runtime. The UI layer sets this from ActualTheme
24+ // so SearchThemeDictionaries resolves the correct theme dictionary.
25+ //internal static bool IsDarkTheme { get; set { } } = true;
26+ internal static bool IsDarkTheme { get ; set ; }
27+ #endif
28+
1729 public string Message { get ; set ; } = string . Empty ;
1830 public string Sender { get ; set ; } = string . Empty ;
31+ public bool IsSystemError { get ; set ; }
1932
20- // Default properties using the helper
21- public Brush BackgroundColor { get ; set ; } = GetBrush ( Colors . White ) ;
22- public Brush SenderColor { get ; set ; } = GetBrush ( Colors . Gray ) ;
33+ // Default properties use theme-aware brushes (with safe fallbacks)
34+ public Brush BackgroundColor { get ; set ; } = GetThemeBrush ( "CardBackgroundFillColorDefaultBrush" , Colors . White ) ;
35+ public Brush SenderColor { get ; set ; } = GetThemeBrush ( "TextFillColorSecondaryBrush" , Colors . Gray ) ;
2336
2437 public HorizontalAlignment Alignment { get ; set ; } = HorizontalAlignment . Left ;
2538 public List < CodeBlock > CodeBlocks { get ; set ; } = new ( ) ;
2639 public bool HasCodeBlocks => CodeBlocks . Count > 0 ;
27- public bool IsAI => Sender == "AI Assistant" ;
40+ public bool IsAI => Sender == AiSender ;
41+ public bool IsUser => Sender == UserSender ;
2842
2943 // --- Factory Methods ---
3044
3145 public static ChatItem CreateUserMessage ( string message ) {
32- return new ChatItem {
46+ return ApplyThemeBrushes ( new ChatItem {
3347 Message = message ,
34- Sender = "You" ,
35- // AliceBlue is a standard "Light Blue"
36- BackgroundColor = GetBrush ( Colors . AliceBlue ) ,
37- // DarkSlateBlue is readable against light backgrounds
38- SenderColor = GetBrush ( Colors . DarkSlateBlue ) ,
48+ Sender = UserSender ,
3949 Alignment = HorizontalAlignment . Right
40- } ;
50+ } ) ;
4151 }
4252
4353 private static Regex FindCodeBlockEnd = new ( @"^```$" , RegexOptions . Multiline ) ;
@@ -46,29 +56,31 @@ public static ChatItem CreateAiMessage(string message) {
4656 // bit hacky to make sure scrollbar doesn't make readability hard
4757 message = FindCodeBlockEnd . Replace ( message , "\n ```" ) ;
4858
49- return new ChatItem {
59+ return ApplyThemeBrushes ( new ChatItem {
5060 Message = message ,
51- Sender = "AI Assistant" ,
52- // WhiteSmoke is a standard "Light Gray" perfect for message bubbles
53- BackgroundColor = GetBrush ( Colors . WhiteSmoke ) ,
54- SenderColor = GetBrush ( Colors . SeaGreen ) ,
61+ Sender = AiSender ,
5562 Alignment = HorizontalAlignment . Left ,
5663 CodeBlocks = CodeBlock . ExtractCodeBlocks ( message )
57- } ;
64+ } ) ;
5865 }
5966
6067 public static ChatItem CreateSystemMessage ( string message , bool isError = false ) {
61- // Setup colors based on error state using Named Colors
62- Color bgColor = isError ? Colors . MistyRose : Colors . Cornsilk ;
63- Color txtColor = isError ? Colors . DarkRed : Colors . DarkOrange ;
64-
65- return new ChatItem {
68+ return ApplyThemeBrushes ( new ChatItem {
6669 Message = message ,
67- Sender = "System" ,
68- BackgroundColor = GetBrush ( bgColor ) ,
69- SenderColor = GetBrush ( txtColor ) ,
70+ Sender = SystemSender ,
71+ IsSystemError = isError ,
7072 Alignment = HorizontalAlignment . Stretch
71- } ;
73+ } ) ;
74+ }
75+
76+ public ChatItem CreateRethemedCopy ( ) {
77+ return ApplyThemeBrushes ( new ChatItem {
78+ Message = Message ,
79+ Sender = Sender ,
80+ IsSystemError = IsSystemError ,
81+ Alignment = Alignment ,
82+ CodeBlocks = CodeBlocks
83+ } ) ;
7284 }
7385
7486 // --- Helpers ---
@@ -79,13 +91,75 @@ public static ChatItem CreateSystemMessage(string message, bool isError = false)
7991 private static Brush GetBrush ( Color color ) {
8092#if WPF
8193 var brush = new SolidColorBrush ( color ) ;
82- // Freezing is important in WPF for performance and thread safety
94+ // Freezing is important in WPF for performance and thread safety
8395 // (similar to how Brushes.White works)
84- brush . Freeze ( ) ;
96+ brush . Freeze ( ) ;
8597 return brush ;
8698#else
8799 return new SolidColorBrush ( color ) ;
88100#endif
89101 }
102+
103+ internal static Brush GetThemeBrush ( string key , Color fallbackColor ) {
104+ #if WPF
105+ if ( Application . Current ? . TryFindResource ( key ) is Brush resourceBrush ) {
106+ return resourceBrush ;
107+ }
108+ #else
109+ // TryGetValue doesn't search ThemeDictionaries inside MergedDictionaries,
110+ // so search explicitly based on the current app theme.
111+ var resources = Application . Current ? . Resources ;
112+ if ( resources != null ) {
113+ string theme = IsDarkTheme ? "Dark" : "Light" ;
114+ if ( SearchThemeDictionaries ( resources , key , theme ) is Brush found )
115+ return found ;
116+ if ( resources . TryGetValue ( key , out object ? resource ) && resource is Brush resourceBrush )
117+ return resourceBrush ;
118+ }
119+ #endif
120+ return GetBrush ( fallbackColor ) ;
121+ }
122+
123+ #if ! WPF
124+ private static Brush ? SearchThemeDictionaries ( ResourceDictionary dict , string key , string theme ) {
125+ if ( dict . ThemeDictionaries . Count > 0 &&
126+ dict . ThemeDictionaries . TryGetValue ( theme , out object ? td ) &&
127+ td is ResourceDictionary themed &&
128+ themed . TryGetValue ( key , out object ? resource ) &&
129+ resource is Brush brush ) {
130+ return brush ;
131+ }
132+ foreach ( var merged in dict . MergedDictionaries ) {
133+ if ( SearchThemeDictionaries ( merged , key , theme ) is Brush found )
134+ return found ;
135+ }
136+ return null ;
137+ }
138+ #endif
139+
140+ private static ChatItem ApplyThemeBrushes ( ChatItem item ) {
141+ if ( item . Sender == UserSender ) {
142+ item . BackgroundColor = GetThemeBrush ( "TextFillColorInverseBrush" , Colors . Orange ) ;
143+ item . SenderColor = GetThemeBrush ( "TextOnAccentFillColorSecondary" , Colors . Gray ) ;
144+ return item ;
145+ }
146+
147+ if ( item . Sender == AiSender ) {
148+ item . BackgroundColor = GetThemeBrush ( "CardBackgroundFillColorSecondaryBrush" , Colors . WhiteSmoke ) ;
149+ item . SenderColor = GetThemeBrush ( "TextFillColorSecondaryBrush" , Colors . SeaGreen ) ;
150+ return item ;
151+ }
152+
153+ if ( item . Sender == SystemSender ) {
154+ string bgKey = item . IsSystemError ? "InfoBarErrorSeverityBackgroundBrush" : "InfoBarWarningSeverityBackgroundBrush" ;
155+ item . BackgroundColor = GetThemeBrush ( bgKey , item . IsSystemError ? Colors . MistyRose : Colors . Cornsilk ) ;
156+ item . SenderColor = GetThemeBrush ( "TextFillColorPrimaryBrush" , item . IsSystemError ? Colors . DarkRed : Colors . DarkOrange ) ;
157+ return item ;
158+ }
159+
160+ item . BackgroundColor = GetThemeBrush ( "CardBackgroundFillColorDefaultBrush" , Colors . White ) ;
161+ item . SenderColor = GetThemeBrush ( "TextFillColorSecondaryBrush" , Colors . Gray ) ;
162+ return item ;
163+ }
90164 }
91165}
0 commit comments