@@ -9,20 +9,27 @@ export class ChatUI {
99 /**
1010 * @param {import('./agent.js').Agent } agent
1111 * @param {Object } config - app config (for model list)
12+ * @param {Object } mount - DOM refs from layout-manager.buildLayout()
13+ * {
14+ * container, messages, input, send, mic, header, footer, footerRight,
15+ * }
1216 */
13- constructor ( agent , config ) {
17+ constructor ( agent , config , mount ) {
1418 this . agent = agent ;
1519 this . config = config ;
1620 this . busy = false ;
1721
18- // Cache DOM refs
19- this . container = document . getElementById ( 'chat-container' ) ;
20- this . messagesEl = document . getElementById ( 'chat-messages' ) ;
21- this . inputEl = document . getElementById ( 'chat-input' ) ;
22- this . sendBtn = document . getElementById ( 'chat-send' ) ;
23- this . modelSelector = document . getElementById ( 'model-selector' ) ;
24- this . toggleBtn = document . getElementById ( 'chat-toggle' ) ;
25- this . micBtn = document . getElementById ( 'chat-mic' ) ;
22+ // Cache DOM refs from layout-manager (no getElementById here).
23+ this . container = mount . container ;
24+ this . messagesEl = mount . messages ;
25+ this . inputEl = mount . input ;
26+ this . sendBtn = mount . send ;
27+ this . micBtn = mount . mic ;
28+ this . toggleBtn = mount . container . querySelector ( '#chat-toggle' ) ; // floating-mode only
29+ this . headerEl = mount . header ;
30+ this . footerEl = mount . footer ;
31+ this . footerRightEl = mount . footerRight ;
32+ this . modelSelector = mount . footerRight . querySelector ( '#model-selector' ) ;
2633
2734 // Voice input state. The voice + transcriber modules are loaded
2835 // lazily via dynamic import() — only when `config.transcription_model`
@@ -64,9 +71,6 @@ export class ChatUI {
6471 // modules are never loaded).
6572 this . initVoiceInput ( ) ;
6673
67- // Restructure footer into left + right zones before adding buttons
68- this . restructureFooter ( ) ;
69-
7074 // If in user-provided API key mode, add settings button
7175 if ( this . config . _userProvidedMode ) {
7276 this . initSettingsUI ( ) ;
@@ -79,9 +83,6 @@ export class ChatUI {
7983 // Auto-approve toggle (always shown)
8084 this . initAutoApproveToggle ( ) ;
8185
82- // Drag-to-resize handle
83- this . initResize ( ) ;
84-
8586 // Optional header/footer links (github, docs, carbon)
8687 this . initLinks ( ) ;
8788
@@ -237,24 +238,6 @@ export class ChatUI {
237238 this . messagesEl . appendChild ( el ) ;
238239 }
239240
240- /* ------------------------------------------------------------------ */
241- /* Footer restructuring (left / right zones) */
242- /* ------------------------------------------------------------------ */
243-
244- restructureFooter ( ) {
245- const footer = document . getElementById ( 'chat-footer' ) ;
246- if ( ! footer ) return ;
247-
248- const right = document . createElement ( 'div' ) ;
249- right . id = 'chat-footer-right' ;
250-
251- // Move model-selector into right zone
252- const modelSelector = footer . querySelector ( '#model-selector' ) ;
253- if ( modelSelector ) right . appendChild ( modelSelector ) ;
254-
255- footer . appendChild ( right ) ;
256- }
257-
258241 /* ------------------------------------------------------------------ */
259242 /* Optional links: github, docs (header), carbon (footer left) */
260243 /* ------------------------------------------------------------------ */
@@ -263,64 +246,56 @@ export class ChatUI {
263246 const links = this . config . links ;
264247 if ( ! links ) return ;
265248
266- // Header links: About (docs) + GitHub octocat
267- if ( links . docs || links . github ) {
268- const headerLinks = document . createElement ( 'div' ) ;
269- headerLinks . className = 'header-links' ;
270-
271- if ( links . docs ) {
272- const a = document . createElement ( 'a' ) ;
273- a . href = links . docs ;
274- a . target = '_blank' ;
275- a . rel = 'noopener noreferrer' ;
276- a . className = 'header-link docs-link' ;
277- a . textContent = 'About' ;
278- a . title = 'Documentation' ;
279- headerLinks . appendChild ( a ) ;
280- }
281-
282- if ( links . github ) {
283- const a = document . createElement ( 'a' ) ;
284- a . href = links . github ;
285- a . target = '_blank' ;
286- a . rel = 'noopener noreferrer' ;
287- a . className = 'header-link github-link' ;
288- a . title = 'Source code' ;
289- // GitHub mark SVG (official)
290- a . innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>` ;
291- headerLinks . appendChild ( a ) ;
292- }
249+ // All links live in the footer-left zone in both floating and sidebar
250+ // modes. The header is kept link-free.
251+ const footer = this . footerEl ;
252+ if ( ! footer ) return ;
293253
294- const header = document . getElementById ( 'chat-header' ) ;
295- const toggleBtn = document . getElementById ( 'chat-toggle' ) ;
296- if ( header && toggleBtn ) {
297- header . insertBefore ( headerLinks , toggleBtn ) ;
298- }
299- }
254+ // Reverse append order: we prepend each link to the footer so that the
255+ // final left-to-right ordering is docs | github | carbon.
256+ // (prepend reverses insertion order — insert carbon first, then github,
257+ // then docs.)
300258
301- // Footer left: carbon dashboard (NRP deployments only)
302259 if ( links . carbon ) {
303- const footer = document . getElementById ( 'chat-footer' ) ;
304- if ( ! footer ) return ;
305-
306260 const a = document . createElement ( 'a' ) ;
307261 a . href = 'https://carbon-api.nrp-nautilus.io/' ;
308262 a . target = '_blank' ;
309263 a . rel = 'noopener noreferrer' ;
310264 a . className = 'footer-link carbon-link' ;
311265 a . title = 'Carbon dashboard — energy use for this deployment' ;
312- // Leaf SVG
313266 a . innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="13" height="13" fill="currentColor" aria-hidden="true"><path d="M17 8C8 10 5.9 16.17 3.82 21.34L5.71 22l1-2.3A4.49 4.49 0 008 20C19 20 22 3 22 3c-1 2-8 5.5-8.5 11.5-2.05-1.05-3.72-3.07-3.72-5.5 0-.67.19-1.3.52-1.83A4.89 4.89 0 0017 8z"/></svg>` ;
314267 footer . prepend ( a ) ;
315268 }
269+
270+ if ( links . github ) {
271+ const a = document . createElement ( 'a' ) ;
272+ a . href = links . github ;
273+ a . target = '_blank' ;
274+ a . rel = 'noopener noreferrer' ;
275+ a . className = 'footer-link github-link' ;
276+ a . title = 'Source code' ;
277+ a . innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>` ;
278+ footer . prepend ( a ) ;
279+ }
280+
281+ if ( links . docs ) {
282+ const a = document . createElement ( 'a' ) ;
283+ a . href = links . docs ;
284+ a . target = '_blank' ;
285+ a . rel = 'noopener noreferrer' ;
286+ a . className = 'footer-link docs-link' ;
287+ a . textContent = 'About' ;
288+ a . title = 'Documentation' ;
289+ footer . prepend ( a ) ;
290+ }
316291 }
317292
318293 /* ------------------------------------------------------------------ */
319294 /* Settings panel (user-provided API key mode) */
320295 /* ------------------------------------------------------------------ */
321296
322297 initSettingsUI ( ) {
323- const footer = document . getElementById ( 'chat-footer-right' ) ;
298+ const footer = this . footerRightEl ;
324299 if ( ! footer ) return ;
325300
326301 const btn = document . createElement ( 'button' ) ;
@@ -433,7 +408,7 @@ export class ChatUI {
433408 /* ------------------------------------------------------------------ */
434409
435410 initAutoApproveToggle ( ) {
436- const footer = document . getElementById ( 'chat-footer-right' ) ;
411+ const footer = this . footerRightEl ;
437412 if ( ! footer ) return ;
438413
439414 // Resolve initial state: localStorage > config
@@ -456,43 +431,6 @@ export class ChatUI {
456431 footer . prepend ( btn ) ;
457432 }
458433
459- /* ------------------------------------------------------------------ */
460-
461- initResize ( ) {
462- const handle = document . createElement ( 'div' ) ;
463- handle . className = 'resize-handle' ;
464- this . container . prepend ( handle ) ;
465-
466- let startX , startY , startW , startH ;
467-
468- const onMove = ( e ) => {
469- const dx = startX - e . clientX ; // positive = dragging left → wider
470- const dy = startY - e . clientY ; // positive = dragging up → taller
471- const maxW = window . innerWidth - 40 ;
472- const maxH = window . innerHeight - 100 ;
473- this . container . style . width = Math . min ( maxW , Math . max ( 280 , startW + dx ) ) + 'px' ;
474- this . container . style . height = Math . min ( maxH , Math . max ( 200 , startH + dy ) ) + 'px' ;
475- } ;
476-
477- const onUp = ( ) => {
478- document . removeEventListener ( 'mousemove' , onMove ) ;
479- document . removeEventListener ( 'mouseup' , onUp ) ;
480- document . body . style . userSelect = '' ;
481- } ;
482-
483- handle . addEventListener ( 'mousedown' , ( e ) => {
484- e . preventDefault ( ) ;
485- startX = e . clientX ;
486- startY = e . clientY ;
487- startW = this . container . offsetWidth ;
488- startH = this . container . offsetHeight ;
489- document . body . style . userSelect = 'none' ;
490- document . addEventListener ( 'mousemove' , onMove ) ;
491- document . addEventListener ( 'mouseup' , onUp ) ;
492- } ) ;
493- }
494-
495- /* ------------------------------------------------------------------ */
496434 /* Send handler */
497435 /* ------------------------------------------------------------------ */
498436
0 commit comments