@@ -2,7 +2,6 @@ import { setIcon } from 'obsidian';
22
33import type { ChatTurnRequest } from '../../../core/runtime/types' ;
44import { t } from '../../../i18n/i18n' ;
5- import { appendMarkdownSnippet } from '../../../shared/markdown/markdown' ;
65import type { ChatState } from '../state/chat-state' ;
76import type { QueuedMessage } from '../state/types' ;
87import type { ImageContextManager } from '../ui/image-context' ;
@@ -42,32 +41,19 @@ export class QueuedMessageController {
4241
4342 const messages = state . queuedMessages ;
4443 if ( messages . length === 0 ) {
44+ state . queuePaused = false ;
4545 containerEl . removeClass ( 'qoderian-visible-flex' ) ;
4646 containerEl . addClass ( 'qoderian-hidden' ) ;
4747 return ;
4848 }
4949
50- const headerEl = containerEl . createDiv ( { cls : 'qoderian-queue-header' } ) ;
51- const toggleEl = headerEl . createEl ( 'button' , {
52- cls : 'qoderian-queue-header-toggle' ,
53- attr : {
54- 'aria-label' : this . collapsed ? t ( 'chat.queue.expand' ) : t ( 'chat.queue.collapse' ) ,
55- title : this . collapsed ? t ( 'chat.queue.expand' ) : t ( 'chat.queue.collapse' ) ,
56- type : 'button' ,
57- } ,
58- } ) ;
59- setIcon ( toggleEl , this . collapsed ? 'chevron-right' : 'chevron-down' ) ;
60- toggleEl . addEventListener ( 'click' , ( event ) => {
61- event . stopPropagation ( ) ;
62- this . collapsed = ! this . collapsed ;
63- this . updateIndicator ( ) ;
64- } ) ;
65- headerEl . createSpan ( {
66- cls : 'qoderian-queue-header-title' ,
67- text : t ( 'chat.queue.title' , { count : messages . length } ) ,
68- } ) ;
50+ if ( state . queuePaused ) {
51+ this . renderPausedHeader ( containerEl ) ;
52+ } else {
53+ this . renderCollapsibleHeader ( containerEl , messages . length ) ;
54+ }
6955
70- if ( ! this . collapsed ) {
56+ if ( ! this . collapsed || state . queuePaused ) {
7157 const listEl = containerEl . createDiv ( { cls : 'qoderian-queue-list' } ) ;
7258 for ( const message of messages ) {
7359 this . renderRow ( listEl , message ) ;
@@ -91,26 +77,85 @@ export class QueuedMessageController {
9177 this . updateIndicator ( ) ;
9278 }
9379
94- /** Withdraw one item back into the composer. */
80+ /** Withdraw one item back into the composer, replacing its content . */
9581 withdrawToComposer ( id : string ) : void {
9682 const { state } = this . deps ;
9783 const target = state . queuedMessages . find ( message => message . id === id ) ;
9884 if ( ! target ) return ;
9985 state . queuedMessages = state . queuedMessages . filter ( message => message . id !== id ) ;
100- this . restoreMessageToInput ( target , true ) ;
86+ this . restoreMessageToInput ( target ) ;
87+ this . updateIndicator ( ) ;
88+ }
89+
90+ /** Suspend auto-drain after the user interrupts a turn (Codex-style pause). */
91+ pause ( ) : void {
92+ const { state } = this . deps ;
93+ if ( state . queuedMessages . length === 0 ) return ;
94+ state . queuePaused = true ;
10195 this . updateIndicator ( ) ;
10296 }
10397
98+ /** Resume auto-drain and immediately send the head entry. */
99+ resume ( ) : void {
100+ const { state } = this . deps ;
101+ state . queuePaused = false ;
102+ this . updateIndicator ( ) ;
103+ this . process ( ) ;
104+ }
105+
104106 /** Drain the head of the queue at turn end. */
105107 process ( ) : void {
106108 const { state } = this . deps ;
109+ if ( state . queuePaused ) return ;
107110 const next = state . queuedMessages [ 0 ] ;
108111 if ( ! next ) return ;
109112 state . queuedMessages = state . queuedMessages . slice ( 1 ) ;
110113 this . updateIndicator ( ) ;
111114 window . setTimeout ( ( ) => this . deps . sendQueuedTurn ( this . toQueuedChatTurn ( next ) ) , 0 ) ;
112115 }
113116
117+ private renderCollapsibleHeader ( containerEl : HTMLElement , count : number ) : void {
118+ const headerEl = containerEl . createDiv ( { cls : 'qoderian-queue-header' } ) ;
119+ const toggleEl = headerEl . createEl ( 'button' , {
120+ cls : 'qoderian-queue-header-toggle' ,
121+ attr : {
122+ 'aria-label' : this . collapsed ? t ( 'chat.queue.expand' ) : t ( 'chat.queue.collapse' ) ,
123+ title : this . collapsed ? t ( 'chat.queue.expand' ) : t ( 'chat.queue.collapse' ) ,
124+ type : 'button' ,
125+ } ,
126+ } ) ;
127+ setIcon ( toggleEl , this . collapsed ? 'chevron-right' : 'chevron-down' ) ;
128+ toggleEl . addEventListener ( 'click' , ( event ) => {
129+ event . stopPropagation ( ) ;
130+ this . collapsed = ! this . collapsed ;
131+ this . updateIndicator ( ) ;
132+ } ) ;
133+ headerEl . createSpan ( {
134+ cls : 'qoderian-queue-header-title' ,
135+ text : t ( 'chat.queue.title' , { count } ) ,
136+ } ) ;
137+ }
138+
139+ private renderPausedHeader ( containerEl : HTMLElement ) : void {
140+ const headerEl = containerEl . createDiv ( { cls : 'qoderian-queue-header qoderian-queue-header-paused' } ) ;
141+ const pauseIconEl = headerEl . createSpan ( { cls : 'qoderian-queue-paused-icon' } ) ;
142+ setIcon ( pauseIconEl , 'pause' ) ;
143+ headerEl . createSpan ( {
144+ cls : 'qoderian-queue-header-title' ,
145+ text : t ( 'chat.queue.paused' ) ,
146+ } ) ;
147+ const resumeEl = headerEl . createEl ( 'button' , {
148+ cls : 'qoderian-queue-resume' ,
149+ attr : { 'aria-label' : t ( 'chat.queue.resume' ) , title : t ( 'chat.queue.resume' ) , type : 'button' } ,
150+ } ) ;
151+ setIcon ( resumeEl , 'play' ) ;
152+ resumeEl . createSpan ( { cls : 'qoderian-queue-resume-label' , text : t ( 'chat.queue.resume' ) } ) ;
153+ resumeEl . addEventListener ( 'click' , ( event ) => {
154+ event . stopPropagation ( ) ;
155+ this . resume ( ) ;
156+ } ) ;
157+ }
158+
114159 private renderRow ( listEl : HTMLElement , message : QueuedMessage ) : void {
115160 const rowEl = listEl . createDiv ( { cls : 'qoderian-queue-row' } ) ;
116161 rowEl . dataset . queueId = message . id ;
@@ -146,19 +191,12 @@ export class QueuedMessageController {
146191 } ) ;
147192 }
148193
149- private restoreMessageToInput ( message : QueuedMessage , mergeWithComposer : boolean ) : void {
194+ private restoreMessageToInput ( message : QueuedMessage ) : void {
150195 const inputEl = this . deps . getInputEl ( ) ;
151- const currentContent = mergeWithComposer ? inputEl . value . trim ( ) : '' ;
152- inputEl . value = currentContent
153- ? appendMarkdownSnippet ( message . content , currentContent )
154- : message . content ;
196+ inputEl . value = message . content ;
155197
156198 const imageContextManager = this . deps . getImageContextManager ( ) ;
157- const currentImages = mergeWithComposer
158- ? ( imageContextManager ?. getAttachedImages ( ) ?? [ ] )
159- : [ ] ;
160- const restoredImages = [ ...( message . images ?? [ ] ) , ...currentImages ] ;
161- if ( restoredImages . length > 0 ) imageContextManager ?. setImages ( restoredImages ) ;
199+ imageContextManager ?. setImages ( [ ...( message . images ?? [ ] ) ] ) ;
162200 this . deps . resetInputHeight ( ) ;
163201 inputEl . focus ( ) ;
164202 }
0 commit comments