@@ -25,9 +25,13 @@ import {
2525import { replaceMentionTokensWithHtml } from '../../../shared/markdown/mention-chip' ;
2626import type { ReferenceChipKind } from '../../../shared/mention/types' ;
2727import { openReferenceChip } from '../../../shared/obsidian/compat' ;
28+ import { TurnChangesModal } from '../changes/turn-changes-modal' ;
29+ import { collectTurnChanges } from '../changes/turn-file-changes' ;
30+ import { turnFileDisplayPath } from '../changes/turn-file-path' ;
2831import { formatConversationDirectoryTitle } from '../utils/conversation-directory-title' ;
2932import { findRewindContext } from '../utils/rewind' ;
3033import { setupCollapsible } from './collapsible' ;
34+ import { renderDiffStats } from './diff-renderer' ;
3135import {
3236 renderStoredAsyncSubagent ,
3337 renderStoredSubagent ,
@@ -425,6 +429,7 @@ export class MessageRenderer {
425429 this . appendResponseFooter ( contentEl , msg ) ;
426430 }
427431
432+ this . updateTurnChangesButton ( msg , contentEl ) ;
428433 this . collapseCompletedTurn ( msg , contentEl ) ;
429434 }
430435
@@ -542,6 +547,60 @@ export class MessageRenderer {
542547 return false ;
543548 }
544549
550+ /** Add or refresh the aggregate file-change entry for a stored or live turn. */
551+ updateTurnChangesButton ( msg : ChatMessage , contentEl : HTMLElement ) : void {
552+ contentEl . querySelector ( '.qoderian-turn-changes-trigger' ) ?. remove ( ) ;
553+ if ( msg . role !== 'assistant' ) return ;
554+
555+ const changes = collectTurnChanges ( msg . toolCalls ) ;
556+ if ( changes . files . length === 0 ) return ;
557+
558+ const button = contentEl . createEl ( 'button' , {
559+ cls : 'qoderian-turn-changes-trigger' ,
560+ attr : {
561+ 'aria-label' : t ( 'chat.changes.button' , { count : changes . files . length } ) ,
562+ type : 'button' ,
563+ } ,
564+ } ) ;
565+
566+ const header = button . createSpan ( { cls : 'qoderian-turn-changes-trigger-header' } ) ;
567+ const icon = header . createSpan ( { cls : 'qoderian-turn-changes-trigger-icon' } ) ;
568+ setIcon ( icon , 'files' ) ;
569+ const summary = header . createSpan ( { cls : 'qoderian-turn-changes-trigger-summary' } ) ;
570+ summary . createSpan ( {
571+ cls : 'qoderian-turn-changes-trigger-title' ,
572+ text : t ( 'chat.changes.edited' , { count : changes . files . length } ) ,
573+ } ) ;
574+ const stats = summary . createSpan ( { cls : 'qoderian-turn-changes-trigger-stats' } ) ;
575+ renderDiffStats ( stats , changes . stats ) ;
576+
577+ const visibleFiles = changes . files . slice ( 0 , 3 ) ;
578+ const fileList = button . createSpan ( { cls : 'qoderian-turn-changes-trigger-files' } ) ;
579+ for ( const file of visibleFiles ) {
580+ const row = fileList . createSpan ( { cls : 'qoderian-turn-changes-trigger-file' } ) ;
581+ row . createSpan ( {
582+ cls : 'qoderian-turn-changes-trigger-path' ,
583+ text : turnFileDisplayPath ( this . app , file ) ,
584+ } ) ;
585+ const fileStats = row . createSpan ( { cls : 'qoderian-turn-changes-trigger-file-stats' } ) ;
586+ renderDiffStats ( fileStats , file . stats ) ;
587+ if ( file . stats . added === 0 && file . stats . removed === 0 ) {
588+ fileStats . setText ( file . diffs . some ( diff => diff . operation === 'delete' )
589+ ? t ( 'chat.changes.deleted' )
590+ : t ( 'chat.changes.changed' ) ) ;
591+ }
592+ }
593+
594+ const remaining = changes . files . length - visibleFiles . length ;
595+ if ( remaining > 0 ) {
596+ const more = button . createSpan ( { cls : 'qoderian-turn-changes-trigger-more' } ) ;
597+ more . createSpan ( { text : t ( 'chat.changes.moreFiles' , { count : remaining } ) } ) ;
598+ const chevron = more . createSpan ( { cls : 'qoderian-turn-changes-trigger-more-icon' } ) ;
599+ setIcon ( chevron , 'chevron-down' ) ;
600+ }
601+ button . addEventListener ( 'click' , ( ) => new TurnChangesModal ( this . app , changes ) . open ( ) ) ;
602+ }
603+
545604 /** Rebuilds the active assistant content after a streamed block is upgraded. */
546605 rerenderAssistantContent ( msg : ChatMessage , contentEl : HTMLElement ) : void {
547606 contentEl . empty ( ) ;
0 commit comments