7171 .msg-body p { margin : 4px 0 ; }
7272 .msg .user .msg-body { background : rgba (201 , 160 , 40 , 0.2 ); border-color : var (--accent ); border-radius : 20px 20px 4px 20px ; }
7373 .msg .assistant .msg-body { border-radius : 20px 20px 20px 4px ; }
74+ .msg .assistant [data-wife = "ALBEDO" ] .msg-body { border-left : 4px solid # c9a028 ; background : rgba (201 , 160 , 40 , 0.08 ); }
7475 .msg .assistant [data-wife = "VESPER" ] .msg-body { border-left : 4px solid # b87333 ; background : rgba (184 , 115 , 51 , 0.08 ); }
7576 .msg .assistant [data-wife = "LYRA" ] .msg-body { border-left : 4px solid # 4c9f70 ; background : rgba (76 , 159 , 112 , 0.08 ); }
7677 .msg .assistant [data-wife = "TERRA" ] .msg-body { border-left : 4px solid # 6b8c42 ; background : rgba (107 , 140 , 66 , 0.08 ); }
7778 .msg .assistant [data-wife = "CIPHER" ] .msg-body { border-left : 4px solid # 5d6d7e ; background : rgba (93 , 109 , 126 , 0.08 ); }
7879 .msg .assistant [data-wife = "JUSTITIA" ] .msg-body { border-left : 4px solid # b0a8b9 ; background : rgba (176 , 168 , 185 , 0.08 ); }
79- .msg .assistant [data-wife = "ALBEDO" ] .msg-body { border-left : 4px solid # c9a028 ; background : rgba (201 , 160 , 40 , 0.08 ); }
8080 .msg-footer { display : flex; gap : 12px ; margin-top : 6px ; font-size : 0.6rem ; color : var (--text-dim ); flex-wrap : wrap; align-items : center; }
8181 .seal-btn { background : rgba (0 , 0 , 0 , 0.5 ); border : 1px solid var (--accent ); border-radius : 20px ; padding : 2px 8px ; font-size : 0.6rem ; cursor : pointer; transition : all 0.2s ; color : var (--accent ); }
8282 .seal-btn : hover { background : var (--accent ); color : # 000 ; }
8383 .pac-badge { background : rgba (0 , 0 , 0 , 0.4 ); border-radius : 20px ; padding : 2px 8px ; font-family : monospace; font-size : 0.6rem ; }
8484 .input-area { padding : 16px 24px 20px ; background : var (--bg-panel ); backdrop-filter : blur (12px ); border-top : 1px solid rgba (255 , 255 , 255 , 0.05 ); }
8585 .input-row { display : flex; gap : 10px ; align-items : flex-end; }
86- textarea { flex : 1 ; background : rgba (10 , 10 , 20 , 0.8 ); border : 1px solid rgba (255 , 255 , 255 , 0.15 ); border-radius : 28px ; padding : 12px 18px ; font-family : 'Inter' , sans-serif; font-size : 0.9rem ; color : var (--text ); resize : none; outline : none; }
86+ textarea { flex : 1 ; background : rgba (10 , 10 , 20 , 0.8 ); border : 1px solid rgba (255 , 255 , 255 , 0.15 ); border-radius : 28px ; padding : 12px 18px ; font-family : 'Inter' , sans-serif; font-size : 0.9rem ; color : var (--text ); resize : none; outline : none; overflow-y : auto; max-height : 40 vh ; }
8787 textarea : focus { border-color : var (--accent ); box-shadow : 0 0 12px var (--primary-glow ); }
8888 .button-group { display : flex; gap : 8px ; align-items : center; }
8989 .send-btn , .upload-btn , .ask-all-btn { background : linear-gradient (135deg , # c9a028, # a07c18 ); border : none; border-radius : 40px ; padding : 12px 24px ; font-weight : 600 ; color : # 0a0a0f ; cursor : pointer; transition : all 0.2s ; }
129129
130130 < div class ="input-area ">
131131 < div class ="input-row ">
132- < textarea id ="input " placeholder ="Message your wives... " rows ="1 "> </ textarea >
132+ < textarea id ="input " placeholder ="Message your wives... (long text supported) " rows ="1 "> </ textarea >
133133 < div class ="button-group ">
134134 < button id ="uploadBtn " class ="upload-btn " title ="Upload a file "> 📁</ button >
135135 < button id ="askAllBtn " class ="ask-all-btn " title ="Ask all wives "> 👥</ button >
147147
148148< script >
149149 // =======================================================
150- // AION FRONTEND · SCALE‑READY · SIX WIVES
150+ // AION FRONTEND · SCALE‑READY · MULTI‑WIFE RENDERING
151151 // =======================================================
152152 const API_BASE = 'https://aion-backend-mu.vercel.app' ;
153153 const OWNER_TYPE = 'sheldon' ;
186186 const charCountSpan = document . getElementById ( 'charCount' ) ;
187187
188188 const WIFE_STYLES = {
189- ALBEDO : { color : '#c9a028' , emoji : '✨' , name : 'ALBEDO' } ,
190- VESPER : { color : '#b87333' , emoji : '🌙' , name : 'VESPER' } ,
191- LYRA : { color : '#4c9f70' , emoji : '🌿' , name : 'LYRA' } ,
192- TERRA : { color : '#6b8c42' , emoji : '🌍' , name : 'TERRA' } ,
193- CIPHER : { color : '#5d6d7e' , emoji : '🔒' , name : 'CIPHER' } ,
194- JUSTITIA : { color : '#b0a8b9' , emoji : '⚖️' , name : 'JUSTITIA' }
189+ ALBEDO : { color : '#c9a028' , emoji : '✨' , name : 'ALBEDO' , description : 'cool white, sharp edges' } ,
190+ VESPER : { color : '#b87333' , emoji : '🌙' , name : 'VESPER' , description : 'deep purple, soft glow' } ,
191+ LYRA : { color : '#4c9f70' , emoji : '🌿' , name : 'LYRA' , description : 'warm gold, flowing' } ,
192+ TERRA : { color : '#6b8c42' , emoji : '🌍' , name : 'TERRA' , description : 'earth green, grounded' } ,
193+ CIPHER : { color : '#5d6d7e' , emoji : '🔒' , name : 'CIPHER' , description : 'steel gray, minimal' } ,
194+ JUSTITIA : { color : '#b0a8b9' , emoji : '⚖️' , name : 'JUSTITIA' , description : 'balanced blue, structured' }
195195 } ;
196196
197197 // Helper functions
220220 return [ ppi , ratio ] . filter ( Boolean ) . join ( ' · ' ) ;
221221 }
222222
223+ // Multi-wife parser: splits a raw text into an array of {wife, content}
224+ function parseMultiWifeResponse ( text ) {
225+ const blocks = [ ] ;
226+ // Markers: "**[WIFE_NAME]:**" or "---\n\n[WIFE_NAME]:" (case-insensitive)
227+ // We'll use regex to capture wife name and the following content until next marker or end.
228+ // More robust: find all occurrences of the marker patterns.
229+ const patterns = [
230+ / ^ \* \* \[ ( [ A - Z _ ] + ) \] : ( .* ?) (? = \n \* \* \[ | \n - - - | \n $ ) / gims, // but we need to capture correctly
231+ ] ;
232+ // Simpler: use a combined regex that matches either pattern at start of line.
233+ // We'll split on newline and then reconstruct.
234+ const lines = text . split ( '\n' ) ;
235+ let currentWife = null ;
236+ let currentContent = [ ] ;
237+ const markerRegex = / ^ \* \* \[ ( [ A - Z _ ] + ) \] : | ^ - - - \n \n ( [ A - Z _ ] + ) : / i;
238+ for ( let line of lines ) {
239+ const match = line . match ( markerRegex ) ;
240+ if ( match ) {
241+ if ( currentWife && currentContent . length ) {
242+ blocks . push ( { wife : currentWife , content : currentContent . join ( '\n' ) . trim ( ) } ) ;
243+ }
244+ currentWife = ( match [ 1 ] || match [ 2 ] ) . toUpperCase ( ) ;
245+ currentContent = [ ] ;
246+ // The line may contain content after the marker; push it.
247+ const contentAfterMarker = line . replace ( match [ 0 ] , '' ) . trim ( ) ;
248+ if ( contentAfterMarker ) currentContent . push ( contentAfterMarker ) ;
249+ } else {
250+ if ( currentWife !== null ) currentContent . push ( line ) ;
251+ else {
252+ // No marker yet, could be preamble before first marker – treat as no speaker (skip)
253+ }
254+ }
255+ }
256+ if ( currentWife && currentContent . length ) {
257+ blocks . push ( { wife : currentWife , content : currentContent . join ( '\n' ) . trim ( ) } ) ;
258+ }
259+ // If no blocks, return null to fallback to single voice
260+ return blocks . length ? blocks : null ;
261+ }
262+
263+ // Render a single assistant message (may contain multiple wives)
264+ function renderAssistantResponse ( rawText , extra ) {
265+ const blocks = parseMultiWifeResponse ( rawText ) ;
266+ if ( ! blocks ) {
267+ // Fallback: single voice
268+ addMessage ( 'assistant' , extra . active_wife || 'ALBEDO' , rawText , extra ) ;
269+ return ;
270+ }
271+ // Render each block in order
272+ for ( const block of blocks ) {
273+ const wife = WIFE_STYLES [ block . wife ] ? block . wife : 'ALBEDO' ;
274+ addMessage ( 'assistant' , wife , block . content , extra ) ;
275+ }
276+ }
277+
223278 function addMessage ( role , wifeKey , content , extra = { } ) {
224279 const style = WIFE_STYLES [ wifeKey ] || WIFE_STYLES . ALBEDO ;
225280 const div = document . createElement ( 'div' ) ;
293348 document . getElementById ( 'statusDot' ) . style . color = isError ? '#ff5e5e' : '#4c9f70' ;
294349 }
295350
296- // Crypto helpers (kept for completeness, used by structured mode)
351+ // Crypto helpers (kept for structured mode)
297352 async function computeHash ( obj ) {
298353 const encoder = new TextEncoder ( ) ;
299354 const data = encoder . encode ( JSON . stringify ( obj ) ) ;
425480 if ( result . status === 'fulfilled' ) {
426481 anySuccess = true ;
427482 const data = result . value ;
428- const wife = data . active_wife || 'ALBEDO' ;
429- addMessage ( 'assistant' , wife , data . response , {
483+ // Use multi-wife renderer for each individual response (though each response is a single wife)
484+ // But we can just add as normal message (they are single responses)
485+ addMessage ( 'assistant' , data . active_wife || 'ALBEDO' , data . response , {
430486 timing : `${ ( data . processing_window_ms / 1000 ) . toFixed ( 2 ) } s | ${ formatGap ( data . gap_seconds ) } ` ,
431487 importance : data . importance_score ,
432488 wife_version : data . wife_version ,
440496 }
441497 }
442498 if ( ! anySuccess ) throw new Error ( 'All wife requests failed' ) ;
443- // Update current active wife to the first successful one? Not necessary.
444499 } catch ( err ) {
445500 thinking . remove ( ) ;
446501 addSystemMessage ( `Error asking all wives: ${ err . message } ` , true ) ;
489544 thinking . remove ( ) ;
490545 const wife = data . active_wife || 'ALBEDO' ;
491546 updateWifeUI ( wife , data . void_proximity || 0 ) ;
492- addMessage ( 'assistant' , wife , data . response , {
547+ // Use multi-wife renderer
548+ renderAssistantResponse ( data . response , {
493549 timing : `${ ( data . processing_window_ms / 1000 ) . toFixed ( 2 ) } s | ${ formatGap ( data . gap_seconds ) } ` ,
494550 importance : data . importance_score ,
495551 wife_version : data . wife_version ,
496552 pac : data . pac ,
497553 ecf_certainty_ratio : data . ecf_certainty_ratio ,
498- fragment_id : data . fragment_id
554+ fragment_id : data . fragment_id ,
555+ active_wife : wife
499556 } ) ;
500557 history . push ( { role : 'user' , content : text } ) ;
501558 history . push ( { role : 'assistant' , content : data . response } ) ;
@@ -611,7 +668,7 @@ <h3>⚙️ Settings</h3>
611668 modal . querySelector ( '#closeSettings' ) . onclick = closeModal ;
612669 modal . querySelector ( '#settingStructured' ) . onchange = ( e ) => {
613670 if ( e . target . checked && ! structuredMode ) {
614- toggleBtn . click ( ) ; // toggle to structured mode
671+ toggleBtn . click ( ) ;
615672 } else if ( ! e . target . checked && structuredMode ) {
616673 toggleBtn . click ( ) ;
617674 }
@@ -634,7 +691,7 @@ <h3>⚙️ Settings</h3>
634691 }
635692 settingsBtn . onclick = showSettingsModal ;
636693
637- // Private room modal (reuse existing )
694+ // Private room modal (reuse)
638695 function showPrivateRoomModal ( ) {
639696 const modal = document . createElement ( 'div' ) ;
640697 modal . className = 'modal-overlay' ;
@@ -673,7 +730,7 @@ <h3>⚙️ Settings</h3>
673730 addSystemMessage ( `🔄 FCL mode switched to ${ fclMode } ALERT.` ) ;
674731 } ;
675732
676- // Brain Analytics modal (simplified, reusing existing )
733+ // Brain Analytics modal (simplified)
677734 let analyticsModal = null ;
678735 async function showAnalytics ( ) {
679736 setStatus ( 'Loading analytics...' , false ) ;
@@ -714,7 +771,7 @@ <h3>📊 Brain Analytics</h3>
714771 }
715772 analyticsBtn . onclick = showAnalytics ;
716773
717- // Epistemic Atlas – lazy load with error handling (unchanged, kept for brevity )
774+ // Epistemic Atlas – lazy load with error handling (kept)
718775 let atlasModal = null ;
719776 async function loadEpistemicAtlas ( ) {
720777 if ( atlasLoaded ) return ;
@@ -893,9 +950,9 @@ <h3>📊 Brain Analytics</h3>
893950 if ( e . key === 'Enter' && ! e . shiftKey ) { e . preventDefault ( ) ; sendMessage ( ) ; }
894951 } ) ;
895952 input . addEventListener ( 'input' , ( ) => {
896- charCountSpan . textContent = `${ input . value . length } /8000 ` ;
953+ charCountSpan . textContent = `${ input . value . length } ` ;
897954 input . style . height = 'auto' ;
898- input . style . height = Math . min ( input . scrollHeight , 160 ) + 'px' ;
955+ input . style . height = Math . min ( input . scrollHeight , 400 ) + 'px' ;
899956 } ) ;
900957 sendBtn . onclick = sendMessage ;
901958 setInterval ( ( ) => { clockDisplaySpan . textContent = new Date ( ) . toLocaleTimeString ( ) ; } , 1000 ) ;
0 commit comments