143143 gap : 12px ;
144144 margin-bottom : 12px ;
145145 }
146- .field-group { flex : 1 ; min-width : 120px ; }
146+ .field-group {
147+ flex : 1 ;
148+ min-width : 120px ;
149+ position : relative;
150+ }
147151 .field-group label {
148152 display : block;
149153 font-size : 10px ;
159163 color : white;
160164 font-size : 12px ;
161165 }
166+ .help-icon {
167+ display : inline-block;
168+ width : 16px ;
169+ text-align : center;
170+ font-size : 10px ;
171+ color : # 888 ;
172+ cursor : help;
173+ margin-left : 4px ;
174+ }
162175 .selected-values {
163176 display : flex;
164177 flex-wrap : wrap;
216229 .itl-fields { flex-direction : column; gap : 8px ; }
217230 .field-group { min-width : auto; }
218231 .input-row button { padding : 8px 12px ; }
232+ .plus-btn { width : 40px ; font-size : 18px ; }
233+ .option-btn { padding : 12px ; }
219234 }
220235 </ style >
221236</ head >
@@ -240,7 +255,7 @@ <h2>AION · Ada‑VELA</h2>
240255 < div id ="structuredUI " class ="structured-ui ">
241256 < div class ="itl-fields ">
242257 < div class ="field-group ">
243- < label > INTENT</ label >
258+ < label > INTENT < span class =" help-icon " title =" What do you want to do? (ask, share, request action, etc.) " > ⓘ </ span > </ label >
244259 < select id ="itlIntent ">
245260 < option value ="ask "> ask</ option >
246261 < option value ="share "> share</ option >
@@ -250,7 +265,7 @@ <h2>AION · Ada‑VELA</h2>
250265 </ select >
251266 </ div >
252267 < div class ="field-group ">
253- < label > DOMAIN</ label >
268+ < label > DOMAIN < span class =" help-icon " title =" Subject area (emergence, integrity, healing, security, law, general) " > ⓘ </ span > </ label >
254269 < select id ="itlDomain ">
255270 < option value ="emergence "> emergence</ option >
256271 < option value ="integrity "> integrity</ option >
@@ -261,7 +276,7 @@ <h2>AION · Ada‑VELA</h2>
261276 </ select >
262277 </ div >
263278 < div class ="field-group ">
264- < label > EMOTION</ label >
279+ < label > EMOTION < span class =" help-icon " title =" Your current emotional state " > ⓘ </ span > </ label >
265280 < select id ="itlEmotion ">
266281 < option value ="neutral "> neutral</ option >
267282 < option value ="curious "> curious</ option >
@@ -270,7 +285,7 @@ <h2>AION · Ada‑VELA</h2>
270285 </ select >
271286 </ div >
272287 < div class ="field-group ">
273- < label > TIME</ label >
288+ < label > TIME < span class =" help-icon " title =" How urgent is this? " > ⓘ </ span > </ label >
274289 < select id ="itlTime ">
275290 < option value ="now "> now</ option >
276291 < option value ="today "> today</ option >
@@ -279,6 +294,7 @@ <h2>AION · Ada‑VELA</h2>
279294 </ div >
280295 </ div >
281296 < div class ="selected-values " id ="selectedValues "> </ div >
297+ < button id ="resetStructuredBtn " style ="background:#2a2a3a; color:#c9a028; margin-top:8px; padding:4px 8px; font-size:11px; "> Reset All</ button >
282298 </ div >
283299
284300 < div class ="status-bar ">
@@ -291,20 +307,25 @@ <h2>AION · Ada‑VELA</h2>
291307 < script >
292308 const API = 'https://aion-backend-mu.vercel.app' ;
293309
294- // Session
310+ // Session with expiry
295311 let sessionId = localStorage . getItem ( 'sessionId' ) ;
296- if ( ! sessionId ) {
312+ let sessionExpiry = localStorage . getItem ( 'sessionExpiry' ) ;
313+ if ( ! sessionId || ! sessionExpiry || Date . now ( ) > parseInt ( sessionExpiry ) ) {
297314 sessionId = 'sess_' + Date . now ( ) + '_' + Math . random ( ) . toString ( 36 ) . substr ( 2 , 6 ) ;
298315 localStorage . setItem ( 'sessionId' , sessionId ) ;
316+ localStorage . setItem ( 'sessionExpiry' , Date . now ( ) + 7 * 24 * 60 * 60 * 1000 ) ;
299317 }
300318 document . getElementById ( 'sessionDisplay' ) . textContent = `Session: ${ sessionId . slice ( - 8 ) } ` ;
301319
302320 // State
303321 let lastResponse = null ;
304322 let isSending = false ;
305323 let structuredModeActive = false ;
306- let lastFragmentId = null ; // stores the ID of the last sent fragment (for replies)
307- let pendingClarification = null ; // stores intercept data when waiting for clarification
324+ let lastFragmentId = null ;
325+ let pendingClarification = null ; // { originalFragmentId, interceptData, depth }
326+ let abortController = null ;
327+ let clarificationDepth = 0 ;
328+ const MAX_CLARIFICATION_DEPTH = 3 ;
308329
309330 // DOM elements
310331 const chatDiv = document . getElementById ( 'chat' ) ;
@@ -321,6 +342,7 @@ <h2>AION · Ada‑VELA</h2>
321342 const itlDomain = document . getElementById ( 'itlDomain' ) ;
322343 const itlEmotion = document . getElementById ( 'itlEmotion' ) ;
323344 const itlTime = document . getElementById ( 'itlTime' ) ;
345+ const resetStructuredBtn = document . getElementById ( 'resetStructuredBtn' ) ;
324346
325347 // Helper: Add message
326348 function addMessage ( role , text , extra = { } ) {
@@ -333,8 +355,8 @@ <h2>AION · Ada‑VELA</h2>
333355 extraHtml += `<span class="vela-badge ${ cls } ">⚖️ VELA: ${ extra . velaResult . status } </span>` ;
334356 }
335357 if ( extra . receipt ) {
336- const receiptStr = JSON . stringify ( extra . receipt ) ;
337- extraHtml += `<span class="receipt-badge" title=" ${ escapeHtml ( receiptStr ) } " >📋 ${ extra . receipt . epistemic_weight || 'receipt' } </span>` ;
358+ const openCount = extra . receipt . still_open || 0 ;
359+ extraHtml += `<span class="receipt-badge" data-receipt=' ${ JSON . stringify ( extra . receipt ) . replace ( / ' / g , "'" ) } ' >📋 ${ extra . receipt . epistemic_weight || 'receipt' } ( ${ openCount } open) </span>` ;
338360 }
339361 div . innerHTML = `<strong>${ role === 'user' ? 'You' : 'AI' } :</strong> ${ escapeHtml ( text ) } ${ extraHtml } ` ;
340362 chatDiv . appendChild ( div ) ;
@@ -403,7 +425,12 @@ <h3>📋 Clarification Required</h3>
403425 }
404426
405427 async function sendClarification ( option , replyToId ) {
406- // Build a new fragment that replies to the original with the chosen clarification
428+ if ( clarificationDepth >= MAX_CLARIFICATION_DEPTH ) {
429+ addSystemMessage ( `⚠️ Max clarifications (${ MAX_CLARIFICATION_DEPTH } ) reached. Please rephrase your original question.` ) ;
430+ pendingClarification = null ;
431+ clarificationDepth = 0 ;
432+ return ;
433+ }
407434 const fragment = {
408435 id : crypto . randomUUID ( ) ,
409436 ts : new Date ( ) . toISOString ( ) ,
@@ -416,7 +443,6 @@ <h3>📋 Clarification Required</h3>
416443 content : { summary : `Clarification: ${ option . text } ` , details : option . text } ,
417444 context : { time_sensitivity : 'now' , stage : 4 , requires : [ 'analysis' ] }
418445 } ;
419- // Send this new fragment to the structured endpoint
420446 try {
421447 const res = await fetch ( `${ API } /api/structured-mode` , {
422448 method : 'POST' ,
@@ -427,7 +453,8 @@ <h3>📋 Clarification Required</h3>
427453 if ( result . response ) {
428454 addMessage ( 'ai' , result . response , { sealHash : true , receipt : result . checkpoint_receipt } ) ;
429455 lastResponse = result . response ;
430- // Update VELA badge if available
456+ clarificationDepth = 0 ;
457+ pendingClarification = null ;
431458 if ( result . pac ) {
432459 const ppi = result . pac . ppi ;
433460 const cls = ppi < 30 ? 'vela-block' : ( ppi < 70 ? 'vela-flag' : 'vela-pass' ) ;
@@ -436,7 +463,7 @@ <h3>📋 Clarification Required</h3>
436463 velaDisplay . innerHTML = `⚖️ VELA: ${ status } ` ;
437464 }
438465 } else if ( result . status === 'CEP_INTERCEPT' ) {
439- // Another intercept – show again (should not happen in a well-formed clarification)
466+ clarificationDepth ++ ;
440467 showClarificationModal ( result , fragment . id ) ;
441468 } else {
442469 addSystemMessage ( `⚠️ Unexpected response: ${ JSON . stringify ( result ) } ` ) ;
@@ -447,7 +474,43 @@ <h3>📋 Clarification Required</h3>
447474 }
448475 }
449476
450- // API calls
477+ // API calls with abort controller and timeout
478+ async function callStructuredMode ( fragment ) {
479+ if ( abortController ) abortController . abort ( ) ;
480+ abortController = new AbortController ( ) ;
481+ const timeoutId = setTimeout ( ( ) => abortController . abort ( ) , 30000 ) ;
482+ try {
483+ const res = await fetch ( `${ API } /api/structured-mode` , {
484+ method : 'POST' ,
485+ headers : { 'Content-Type' : 'application/json' } ,
486+ body : JSON . stringify ( fragment ) ,
487+ signal : abortController . signal
488+ } ) ;
489+ clearTimeout ( timeoutId ) ;
490+ if ( ! res . ok ) {
491+ const errText = await res . text ( ) ;
492+ throw new Error ( `HTTP ${ res . status } : ${ errText } ` ) ;
493+ }
494+ return await res . json ( ) ;
495+ } catch ( err ) {
496+ clearTimeout ( timeoutId ) ;
497+ if ( err . name === 'AbortError' ) throw new Error ( 'Request timed out (30s)' ) ;
498+ throw err ;
499+ } finally {
500+ abortController = null ;
501+ }
502+ }
503+
504+ async function callAssistant ( message ) {
505+ const res = await fetch ( `${ API } /api/assistant` , {
506+ method : 'POST' ,
507+ headers : { 'Content-Type' : 'application/json' } ,
508+ body : JSON . stringify ( { message, sessionId, ownerType : 'sheldon' , history : [ ] } )
509+ } ) ;
510+ if ( ! res . ok ) throw new Error ( await res . text ( ) ) ;
511+ return res . json ( ) ;
512+ }
513+
451514 async function sealMessage ( content ) {
452515 try {
453516 const res = await fetch ( `${ API } /api/stp-seal` , {
@@ -479,27 +542,7 @@ <h3>📋 Clarification Required</h3>
479542 } catch ( err ) { showToast ( `Validation failed: ${ err . message } ` , true ) ; }
480543 }
481544
482- async function callAssistant ( message ) {
483- const res = await fetch ( `${ API } /api/assistant` , {
484- method : 'POST' ,
485- headers : { 'Content-Type' : 'application/json' } ,
486- body : JSON . stringify ( { message, sessionId, ownerType : 'sheldon' , history : [ ] } )
487- } ) ;
488- if ( ! res . ok ) throw new Error ( await res . text ( ) ) ;
489- return res . json ( ) ;
490- }
491-
492- async function callStructuredMode ( fragment ) {
493- const res = await fetch ( `${ API } /api/structured-mode` , {
494- method : 'POST' ,
495- headers : { 'Content-Type' : 'application/json' } ,
496- body : JSON . stringify ( fragment )
497- } ) ;
498- if ( ! res . ok ) throw new Error ( await res . text ( ) ) ;
499- return res . json ( ) ;
500- }
501-
502- // Build structured fragment from UI
545+ // Build structured fragment
503546 function buildStructuredFragment ( rawMessage , replyTo = null ) {
504547 const frag = {
505548 id : crypto . randomUUID ( ) ,
@@ -525,17 +568,25 @@ <h3>📋 Clarification Required</h3>
525568 ` ;
526569 }
527570
571+ let lastSendTime = 0 ;
572+ const SEND_DELAY = 300 ; // ms
528573 // Main send
529574 async function send ( ) {
530575 if ( isSending ) { showToast ( 'Already sending...' ) ; return ; }
576+ const now = Date . now ( ) ;
577+ if ( now - lastSendTime < SEND_DELAY ) {
578+ showToast ( 'Please wait a moment before sending again.' ) ;
579+ return ;
580+ }
581+ lastSendTime = now ;
582+
531583 const rawMessage = inputField . value . trim ( ) ;
532584 if ( ! rawMessage ) return ;
533585
534586 inputField . value = '' ;
535587 inputField . disabled = true ;
536588 isSending = true ;
537589
538- // Add user message immediately
539590 addMessage ( 'user' , rawMessage ) ;
540591
541592 const thinking = document . createElement ( 'div' ) ;
@@ -549,12 +600,13 @@ <h3>📋 Clarification Required</h3>
549600 if ( structuredModeActive ) {
550601 const fragment = buildStructuredFragment ( rawMessage , pendingClarification ? pendingClarification . originalFragmentId : null ) ;
551602 result = await callStructuredMode ( fragment ) ;
552- lastFragmentId = fragment . id ; // store for potential replies
603+ lastFragmentId = fragment . id ;
553604 if ( result . status === 'CEP_INTERCEPT' ) {
554605 thinking . remove ( ) ;
555606 pendingClarification = {
556607 originalFragmentId : fragment . id ,
557- interceptData : result
608+ interceptData : result ,
609+ depth : clarificationDepth
558610 } ;
559611 showClarificationModal ( result , fragment . id ) ;
560612 inputField . disabled = false ;
@@ -569,7 +621,6 @@ <h3>📋 Clarification Required</h3>
569621 lastResponse = result . response ;
570622 thinking . remove ( ) ;
571623
572- // Update VELA badge
573624 if ( result . pac ) {
574625 const ppi = result . pac . ppi ;
575626 const cls = ppi < 30 ? 'vela-block' : ( ppi < 70 ? 'vela-flag' : 'vela-pass' ) ;
@@ -606,7 +657,8 @@ <h3>📋 Clarification Required</h3>
606657 inputField . placeholder = 'Type your message (will be structured)' ;
607658 updateSelectedValues ( ) ;
608659 addSystemMessage ( '🌌 **STRUCTURED MODE ACTIVE**\n\nSet intent, domain, emotion, time. Your message will be sent as a structured fragment.' ) ;
609- pendingClarification = null ; // reset any pending intercept
660+ pendingClarification = null ;
661+ clarificationDepth = 0 ;
610662 } else {
611663 structuredUI . classList . remove ( 'active' ) ;
612664 modeBadge . textContent = '⚡ REGULAR' ;
@@ -624,18 +676,27 @@ <h3>📋 Clarification Required</h3>
624676 }
625677
626678 function showTrace ( ) {
627- // Simple trace – could be expanded with actual operation history from backend
628679 const traceInfo = {
629680 sessionId,
630681 structuredModeActive,
631682 lastFragmentId,
632683 lastResponsePreview : lastResponse ?. substring ( 0 , 100 ) ,
633- pendingClarification : pendingClarification ? true : false
684+ pendingClarification : pendingClarification ? true : false ,
685+ clarificationDepth
634686 } ;
635687 showModal ( 'Ada‑VELA Trace' , JSON . stringify ( traceInfo , null , 2 ) ) ;
636688 console . log ( 'Trace data:' , traceInfo ) ;
637689 }
638690
691+ function resetStructuredFields ( ) {
692+ itlIntent . selectedIndex = 0 ;
693+ itlDomain . selectedIndex = 5 ; // general
694+ itlEmotion . selectedIndex = 0 ;
695+ itlTime . selectedIndex = 0 ;
696+ updateSelectedValues ( ) ;
697+ addSystemMessage ( 'Structured mode fields reset.' ) ;
698+ }
699+
639700 // Event listeners
640701 sendBtn . onclick = send ;
641702 plusBtn . onclick = ( e ) => { e . stopPropagation ( ) ; dropdown . style . display = dropdown . style . display === 'flex' ? 'none' : 'flex' ; } ;
@@ -646,6 +707,7 @@ <h3>📋 Clarification Required</h3>
646707 document . getElementById ( 'traceBtn' ) . onclick = ( ) => { dropdown . style . display = 'none' ; showTrace ( ) ; } ;
647708 inputField . addEventListener ( 'keypress' , ( e ) => { if ( e . key === 'Enter' ) send ( ) ; } ) ;
648709 [ itlIntent , itlDomain , itlEmotion , itlTime ] . forEach ( el => el . addEventListener ( 'change' , updateSelectedValues ) ) ;
710+ resetStructuredBtn . onclick = resetStructuredFields ;
649711
650712 // Initial message
651713 addMessage ( 'ai' , 'Ada‑VELA ready. Click + → Structured Mode to activate GATE-ITL.' ) ;
0 commit comments