@@ -12,6 +12,7 @@ import 'prismjs/components/prism-css'
1212import 'prismjs/components/prism-markup'
1313import '@sim/emcn/components/code/code.css'
1414import { Checkbox , CopyCodeButton , cn , highlight , languages } from '@sim/emcn'
15+ import { decodeVfsSegmentSafe } from '@/lib/copilot/vfs/path-utils'
1516import { extractTextContent } from '@/lib/core/utils/react-node-text'
1617import { ContextMentionIcon } from '@/app/workspace/[workspaceId]/home/components/context-mention-icon'
1718import {
@@ -144,6 +145,20 @@ const WSRES_LINK_KINDS: Record<string, ChatContextKind | undefined> = {
144145 file : 'file' ,
145146}
146147
148+ /**
149+ * Label used to pick a file link's extension-aware document icon. The visible
150+ * link text can be a custom title without an extension, so prefer the file
151+ * name carried in the link's VFS path (its last extension-bearing segment).
152+ */
153+ function fileIconLabel ( ref : string , fallback : string ) : string {
154+ const segments = ref . split ( '/' ) . filter ( Boolean )
155+ for ( let i = segments . length - 1 ; i >= 0 ; i -- ) {
156+ const decoded = decodeVfsSegmentSafe ( segments [ i ] )
157+ if ( decoded . includes ( '.' ) ) return decoded
158+ }
159+ return fallback
160+ }
161+
147162const MARKDOWN_COMPONENTS = {
148163 table ( { children } : { children ?: React . ReactNode } ) {
149164 return (
@@ -214,7 +229,10 @@ const MARKDOWN_COMPONENTS = {
214229 } ,
215230 a ( { children, href } : { children ?: React . ReactNode ; href ?: string } ) {
216231 if ( href ?. startsWith ( '#wsres-' ) ) {
217- const kind = WSRES_LINK_KINDS [ href . match ( / ^ # w s r e s - ( \w + ) - / ) ?. [ 1 ] ?? '' ]
232+ const match = href . match ( / ^ # w s r e s - ( \w + ) - ( .+ ) $ / )
233+ const type = match ?. [ 1 ]
234+ const ref = match ?. [ 2 ]
235+ const kind = type ? WSRES_LINK_KINDS [ type ] : undefined
218236 const label = extractTextContent ( children )
219237 return (
220238 < a
@@ -227,25 +245,21 @@ const MARKDOWN_COMPONENTS = {
227245 ) }
228246 onClick = { ( e ) => {
229247 e . preventDefault ( )
230- const match = href . match ( / ^ # w s r e s - ( \w + ) - ( .+ ) $ / )
231- if ( match ) {
232- const type = match [ 1 ]
233- const ref = match [ 2 ]
234- const linkText = label || ref
235- window . dispatchEvent (
236- new CustomEvent ( 'wsres-click' , {
237- detail :
238- type === 'file'
239- ? { type, path : ref , title : linkText }
240- : { type, id : ref , title : linkText } ,
241- } )
242- )
243- }
248+ if ( ! type || ! ref ) return
249+ const linkText = label || ref
250+ window . dispatchEvent (
251+ new CustomEvent ( 'wsres-click' , {
252+ detail :
253+ type === 'file'
254+ ? { type, path : ref , title : linkText }
255+ : { type, id : ref , title : linkText } ,
256+ } )
257+ )
244258 } }
245259 >
246- { kind && (
260+ { kind && ref && (
247261 < ContextMentionIcon
248- context = { { kind, label } }
262+ context = { { kind, label : kind === 'file' ? fileIconLabel ( ref , label ) : label } }
249263 className = 'size-[14px] flex-shrink-0 text-[var(--text-icon)]'
250264 />
251265 ) }
0 commit comments