3333 .card {background : var (--panel );border : 1px solid var (--line );border-radius : 12px ;padding : 20px 22px }
3434 .card h2 {margin : 0 0 4px ;font-size : 12px ;font-weight : 650 ;text-transform : uppercase;
3535 letter-spacing : .08em ;color : var (--muted )}
36- .smiles {font-family : var (--mono );font-size : 13px ;color : var (--muted );margin : 0 0 16 px ;
36+ .smiles {font-family : var (--mono );font-size : 13px ;color : var (--muted );margin : 0 0 4 px ;
3737 word-break : break-all}
38+ .cname {font-size : 16px ;font-weight : 650 ;color : var (--ink );margin : 0 0 4px }
39+ .iupac {font-family : var (--mono );font-size : 11px ;color : var (--muted );margin : 0 0 14px ;word-break : break-all}
3840 .meter {margin : 13px 0 }
3941 .meter .row {display : flex;justify-content : space-between;align-items : baseline;margin-bottom : 5px }
4042 .meter .name {font-weight : 600 ;font-size : 14px ;text-transform : capitalize}
5254 .neighbor {display : flex;align-items : center;gap : 12px ;
5355 padding : 11px 0 ;border-bottom : 1px solid var (--line )}
5456 .neighbor : last-child {border-bottom : none}
57+ .neighbor {cursor : pointer}
58+ .neighbor : hover {background : var (--accent-soft )}
5559 .neighbor .nb-struct {flex : 0 0 auto;width : 64px ;height : 48px ;background : # fff ;border : 1px solid var (--line );border-radius : 6px }
5660 .neighbor .nb-struct svg {width : 64px ;height : 48px }
5761 .neighbor .nb-info {flex : 1 ;min-width : 0 }
@@ -101,7 +105,9 @@ <h1>Flavor Workbench</h1>
101105 < div class ="card ">
102106 < h2 > Flavor read</ h2 >
103107 < div class ="structure " id ="structure "> </ div >
108+ < div class ="cname " id ="cname "> </ div >
104109 < p class ="smiles " id ="smiles "> </ p >
110+ < p class ="iupac " id ="iupac "> </ p >
105111 < div id ="domainBanner " class ="domain-banner " style ="display:none "> </ div >
106112 < div id ="meters "> </ div >
107113 < div class ="chips " id ="chips "> </ div >
@@ -171,13 +177,14 @@ <h2>Aroma</h2>
171177 go . disabled = true ; go . textContent = 'Reading…' ;
172178 $ ( 'err' ) . style . display = 'none' ;
173179 try {
174- const [ p , n , s ] = await Promise . all ( [
180+ const [ p , n , s , nm ] = await Promise . all ( [
175181 post ( '/api/predict' , { smiles :text } ) ,
176182 post ( '/api/neighbors' , { smiles :text , k :8 } ) ,
177- post ( '/api/structure' , { smiles :text } )
183+ post ( '/api/structure' , { smiles :text } ) ,
184+ post ( '/api/names' , { smiles :text } )
178185 ] ) ;
179186 if ( p . error ) { throw new Error ( p . error ) ; }
180- render ( p , n . neighbors || [ ] , ( s && s . svg ) || null ) ;
187+ render ( p , n . neighbors || [ ] , ( s && s . svg ) || null , nm || { } ) ;
181188 } catch ( e ) {
182189 $ ( 'results' ) . style . display = 'none' ; $ ( 'behaviorCard' ) . style . display = 'none' ; $ ( 'aromaCard' ) . style . display = 'none' ; $ ( 'footnote' ) . style . display = 'none' ;
183190 $ ( 'err' ) . textContent = e . message ; $ ( 'err' ) . style . display = 'block' ;
@@ -186,9 +193,14 @@ <h2>Aroma</h2>
186193 }
187194}
188195
189- function render ( p , neighbors , svg ) {
196+ function render ( p , neighbors , svg , names ) {
197+ names = names || { } ;
190198 $ ( 'structure' ) . innerHTML = svg || '' ;
199+ $ ( 'cname' ) . textContent = names . common || '' ;
200+ $ ( 'cname' ) . style . display = names . common ? 'block' : 'none' ;
191201 $ ( 'smiles' ) . textContent = p . smiles ;
202+ $ ( 'iupac' ) . textContent = names . iupac ? ( 'IUPAC: ' + names . iupac ) : '' ;
203+ $ ( 'iupac' ) . style . display = names . iupac ? 'block' : 'none' ;
192204 const inDomain = ! ( p . applicability && p . applicability . in_domain === false ) ;
193205 const db = $ ( 'domainBanner' ) ;
194206 db . style . display = inDomain ? 'none' : 'block' ;
@@ -211,8 +223,10 @@ <h2>Aroma</h2>
211223 p . sour && p . sour_reason && p . sour_reason . length ? `sour · ${ p . sour_reason [ 0 ] } ` : 'not sour' ) ) ;
212224 c . push ( chip ( 'salty' , p . salty === true , p . salty === true ?'var(--salty)' :null ,
213225 p . salty === true ? 'salty (known)' : 'salty — not in data' ) ) ;
214- if ( inDomain && typeof p . sweet_intensity === 'number' )
215- c . push ( chip ( 'intensity' , true , 'var(--sweet)' , `sweetness ×${ p . sweet_intensity } vs sucrose` ) ) ;
226+ if ( inDomain && p . sweet >= 0.5 && typeof p . sweet_intensity === 'number' ) {
227+ const x = Math . pow ( 10 , p . sweet_intensity ) ;
228+ c . push ( chip ( 'intensity' , true , 'var(--sweet)' , `~${ x >= 10 ?Math . round ( x ) :x . toFixed ( 1 ) } × as sweet as sucrose` ) ) ;
229+ }
216230 if ( inDomain && p . multitaste ) c . push ( chip ( 'multi' , true , 'var(--aroma)' , 'multi-taste' ) ) ;
217231 $ ( 'chips' ) . innerHTML = c . join ( '' ) ;
218232
@@ -221,7 +235,7 @@ <h2>Aroma</h2>
221235 $ ( 'neighbors' ) . innerHTML = '<div class="empty">Build taste_master.parquet to enable substitution search.</div>' ;
222236 } else {
223237 $ ( 'neighbors' ) . innerHTML = neighbors . map ( x => `
224- <div class="neighbor">
238+ <div class="neighbor" data-smi=" ${ x . smiles } " title="Click to analyze this molecule" >
225239 <div class="nb-struct">${ x . svg || '' } </div>
226240 <div class="nb-info">
227241 <div class="nb-name">${ x . name || '—' } </div>
@@ -270,6 +284,11 @@ <h2>Aroma</h2>
270284
271285go . addEventListener ( 'click' , run ) ;
272286q . addEventListener ( 'keydown' , e => { if ( e . key === 'Enter' ) run ( ) ; } ) ;
287+ // click a substitution candidate -> analyze that molecule
288+ $ ( 'neighbors' ) . addEventListener ( 'click' , e => {
289+ const nb = e . target . closest ( '.neighbor' ) ;
290+ if ( nb && nb . dataset . smi ) { q . value = nb . dataset . smi ; window . scrollTo ( { top :0 , behavior :'smooth' } ) ; run ( ) ; }
291+ } ) ;
273292</ script >
274293</ body >
275294</ html >
0 commit comments