@@ -50,6 +50,32 @@ const TOOLKIT_OPTIONS = [
5050// Converter options for IUPAC
5151const IUPAC_CONVERTER_OPTIONS = [ { id : "opsin" , label : "OPSIN" } ] ;
5252
53+ // Detect whether input text is SMILES, SELFIES, or IUPAC name
54+ const detectInputFormat = ( text ) => {
55+ const trimmed = text . trim ( ) ;
56+ if ( ! trimmed ) return null ;
57+
58+ // SELFIES: entirely composed of [token] sequences
59+ if ( / ^ ( \[ [ \w @ = # + \- / \\ % ; . , ^ * ] + \] ) + $ / . test ( trimmed ) ) return "selfies" ;
60+
61+ // IUPAC: word-like name — only lowercase letters, digits, spaces, hyphens, commas,
62+ // parentheses; and no SMILES ring-closure patterns (letter immediately adjacent to digit)
63+ if (
64+ / ^ [ a - z 0 - 9 ] [ a - z 0 - 9 , \- ( ) . ] * $ / . test ( trimmed ) &&
65+ ! / [ a - z ] \d / . test ( trimmed ) &&
66+ ! / \d [ a - z ] / . test ( trimmed )
67+ ) {
68+ return "iupac" ;
69+ }
70+
71+ // SMILES: no whitespace, only valid SMILES characters
72+ if ( ! / \s / . test ( trimmed ) && / ^ [ A - Z a - z 0 - 9 [ \] ( ) = # @ + \- / . \\ % : * ] + $ / . test ( trimmed ) )
73+ return "smiles" ;
74+
75+ // Otherwise assume IUPAC name
76+ return "iupac" ;
77+ } ;
78+
5379const FormatConversionView = ( ) => {
5480 const [ input , setInput ] = useState ( "" ) ;
5581 const [ inputFormat , setInputFormat ] = useState ( "smiles" ) ;
@@ -60,6 +86,7 @@ const FormatConversionView = () => {
6086 const [ loading , setLoading ] = useState ( false ) ;
6187 const [ error , setError ] = useState ( null ) ;
6288 const [ copied , setCopied ] = useState ( false ) ;
89+ const [ autoDetected , setAutoDetected ] = useState ( false ) ;
6390 // State for molecular structure display
6491 const [ smilesForStructure , setSmilesForStructure ] = useState ( "" ) ;
6592 const [ showStructure , setShowStructure ] = useState ( false ) ;
@@ -170,6 +197,7 @@ const FormatConversionView = () => {
170197 // When input format changes, automatically update output format if needed
171198 const handleInputFormatChange = ( format ) => {
172199 setInputFormat ( format ) ;
200+ setAutoDetected ( false ) ; // User manually overrode auto-detection
173201
174202 // Clear uploaded file when switching away from molsdf
175203 if ( format !== "molsdf" ) {
@@ -182,6 +210,27 @@ const FormatConversionView = () => {
182210 }
183211 } ;
184212
213+ // Handle text input changes with auto-format detection
214+ const handleInputChange = ( value ) => {
215+ setInput ( value ) ;
216+ if ( inputFormat === "molsdf" ) return ;
217+
218+ const detected = detectInputFormat ( value ) ;
219+ if ( ! value . trim ( ) ) {
220+ setAutoDetected ( false ) ;
221+ return ;
222+ }
223+ if ( detected && detected !== inputFormat ) {
224+ setInputFormat ( detected ) ;
225+ setAutoDetected ( true ) ;
226+ if ( detected === "iupac" || detected === "selfies" ) {
227+ setOutputFormat ( "smiles" ) ;
228+ } else if ( detected === "smiles" ) {
229+ setOutputFormat ( "canonicalsmiles" ) ;
230+ }
231+ }
232+ } ;
233+
185234 // When output format changes, we may need to adjust toolkit availability
186235 const handleOutputFormatChange = ( format ) => {
187236 setOutputFormat ( format ) ;
@@ -365,12 +414,19 @@ const FormatConversionView = () => {
365414 < form onSubmit = { handleSubmit } className = "space-y-4" >
366415 { /* Input Format Selection */ }
367416 < div >
368- < label
369- htmlFor = "input-format-select"
370- className = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
371- >
372- Input Format
373- </ label >
417+ < div className = "flex items-center justify-between mb-1" >
418+ < label
419+ htmlFor = "input-format-select"
420+ className = "block text-sm font-medium text-gray-700 dark:text-gray-300"
421+ >
422+ Input Format
423+ </ label >
424+ { autoDetected && (
425+ < span className = "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300" >
426+ Auto-detected
427+ </ span >
428+ ) }
429+ </ div >
374430 < select
375431 id = "input-format-select"
376432 value = { inputFormat }
@@ -498,7 +554,7 @@ const FormatConversionView = () => {
498554 < >
499555 < SMILESInput
500556 value = { input }
501- onChange = { setInput }
557+ onChange = { handleInputChange }
502558 label = {
503559 inputFormat === "smiles"
504560 ? "SMILES Input"
@@ -700,7 +756,7 @@ const FormatConversionView = () => {
700756 Converted from{ " " }
701757 { INPUT_FORMAT_OPTIONS . find ( ( o ) => o . id === inputFormat ) ?. label ||
702758 inputFormat . toUpperCase ( ) }
703- to{ " " }
759+ { " " } to{ " " }
704760 { OUTPUT_FORMAT_OPTIONS . find ( ( o ) => o . id === outputFormat ) ?. label ||
705761 outputFormat . toUpperCase ( ) }
706762 { showToolkitSelection &&
0 commit comments