@@ -7,6 +7,11 @@ import {
77}  from  "@angular/core" ; 
88import  {  CommonModule  }  from  "@angular/common" ; 
99import  {  cn  }  from  "../../utils" ; 
10+ import  type  {  BinaryInputContent ,  InputContent  }  from  "@ag-ui/client" ; 
11+ import  { 
12+   getUserMessageBinaryContents , 
13+   getUserMessageTextContent , 
14+ }  from  "@copilotkitnext/shared" ; 
1015
1116@Component ( { 
1217  selector : "copilot-chat-user-message-renderer" , 
@@ -17,10 +22,54 @@ import { cn } from "../../utils";
1722  host : { 
1823    "[class]" : "computedClass()" , 
1924  } , 
20-   template : `{{ content() }}` , 
25+   template : ` 
26+     @if (textContent()) { 
27+       <span>{{ textContent() }}</span> 
28+     } 
29+     @if (attachments().length) { 
30+       <div [class]="attachmentsClass()"> 
31+         @for (attachment of attachments(); track trackAttachment(attachment, $index)) { 
32+           <ng-container *ngIf="isImage(attachment); else fileTemplate"> 
33+             <figure class="flex flex-col gap-1"> 
34+               <img 
35+                 [src]="resolveSource(attachment)" 
36+                 [alt]="attachment.filename || attachment.id || attachment.mimeType" 
37+                 class="max-h-64 rounded-lg border border-border object-contain" 
38+               /> 
39+               @if (attachment.filename || attachment.id) { 
40+                 <figcaption class="text-xs text-muted-foreground"> 
41+                   {{ attachment.filename || attachment.id }} 
42+                 </figcaption> 
43+               } 
44+             </figure> 
45+           </ng-container> 
46+           <ng-template #fileTemplate> 
47+             <div class="rounded-md border border-dashed border-border bg-muted/70 px-3 py-2 text-xs text-muted-foreground"> 
48+               {{ attachment.filename || attachment.id || 'Attachment' }} 
49+               <span class="block text-[10px] uppercase tracking-wide text-muted-foreground/70"> 
50+                 {{ attachment.mimeType }} 
51+               </span> 
52+               @if (resolveSource(attachment) && !isImage(attachment)) { 
53+                 <a 
54+                   [href]="resolveSource(attachment)" 
55+                   target="_blank" 
56+                   rel="noreferrer" 
57+                   class="mt-1 block text-xs text-primary underline" 
58+                 > 
59+                   Open 
60+                 </a> 
61+               } 
62+             </div> 
63+           </ng-template> 
64+         } 
65+       </div> 
66+     } 
67+   ` , 
2168} ) 
2269export  class  CopilotChatUserMessageRenderer  { 
2370  readonly  content  =  input < string > ( "" ) ; 
71+   readonly  contents  =  input < InputContent [ ] > ( [ ] ) ; 
72+   readonly  attachments  =  input < BinaryInputContent [ ]  |  undefined > ( undefined ) ; 
2473  readonly  inputClass  =  input < string  |  undefined > ( ) ; 
2574
2675  readonly  computedClass  =  computed ( ( )  =>  { 
@@ -29,4 +78,44 @@ export class CopilotChatUserMessageRenderer {
2978      this . inputClass ( ) 
3079    ) ; 
3180  } ) ; 
81+ 
82+   readonly  textContent  =  computed ( ( )  =>  { 
83+     const  explicit  =  this . content ( ) ; 
84+     if  ( explicit  &&  explicit . length  >  0 )  { 
85+       return  explicit ; 
86+     } 
87+     return  getUserMessageTextContent ( this . contents ( ) ) ; 
88+   } ) ; 
89+ 
90+   readonly  attachments  =  computed ( ( )  =>  { 
91+     const  provided  =  this . attachments ( )  ??  [ ] ; 
92+     if  ( provided . length  >  0 )  { 
93+       return  provided ; 
94+     } 
95+     return  getUserMessageBinaryContents ( this . contents ( ) ) ; 
96+   } ) ; 
97+ 
98+   readonly  attachmentsClass  =  computed ( ( )  => 
99+     this . textContent ( ) . trim ( ) . length  >  0 
100+       ? "mt-3 flex flex-col gap-2" 
101+       : "flex flex-col gap-2" , 
102+   ) ; 
103+ 
104+   resolveSource ( attachment : BinaryInputContent ) : string  |  null  { 
105+     if  ( attachment . url )  { 
106+       return  attachment . url ; 
107+     } 
108+     if  ( attachment . data )  { 
109+       return  `data:${ attachment . mimeType }  ;base64,${ attachment . data }  ` ; 
110+     } 
111+     return  null ; 
112+   } 
113+ 
114+   isImage ( attachment : BinaryInputContent ) : boolean  { 
115+     return  attachment . mimeType . startsWith ( "image/" )  &&  ! ! this . resolveSource ( attachment ) ; 
116+   } 
117+ 
118+   trackAttachment ( attachment : BinaryInputContent ,  index : number ) : string  { 
119+     return  attachment . id  ??  attachment . url  ??  attachment . filename  ??  index . toString ( ) ; 
120+   } 
32121} 
0 commit comments