@@ -35,11 +35,13 @@ import {
3535 getIsLogin ,
3636 loadAuthStateFromStorage ,
3737 setAuthState ,
38+ getUserInfo ,
3839} from '@/features/auth/authStore'
3940import { usePromptingSettings } from '@/hooks/usePromptingSettings'
4041import { checkLocation , isSensetimeOrchestrator } from '@/utils/location'
4142import { ConfirmDialog } from './components/common/Dialog'
4243import { useTranslation } from 'react-i18next'
44+ import { fetchGetMissingSecret } from '@/request/api'
4345
4446const link = {
4547 href : 'https://github.com/dlp3d-ai/dlp3d.ai' ,
@@ -62,6 +64,7 @@ export default function Home() {
6264
6365 const isChatStarting = useSelector ( getIsChatStarting )
6466 const isLogin = useSelector ( getIsLogin )
67+ const user = useSelector ( getUserInfo )
6568 const selectedModelIndex = useSelector ( getSelectedModelIndex )
6669 const selectedCharacterId = useSelector ( getSelectedCharacterId )
6770 const isCharacterLoading = useSelector ( getIsCharacterLoading )
@@ -86,6 +89,16 @@ export default function Home() {
8689 const { loadUserCharacters } = usePromptingSettings ( )
8790 const [ selectedScene , setSelectedScene ] = useState ( 3 ) // Parameter for scene navigation
8891 const [ locationDialogOpen , setLocationDialogOpen ] = useState ( false )
92+ const [ missingSecretDialogOpen , setMissingSecretDialogOpen ] = useState ( false )
93+ const [ missingSecret , setMissingSecret ] = useState < {
94+ llm_requirements : string [ ]
95+ tts_requirements : string [ ]
96+ asr_requirements : string [ ]
97+ } > ( {
98+ llm_requirements : [ ] ,
99+ tts_requirements : [ ] ,
100+ asr_requirements : [ ] ,
101+ } )
89102
90103 useEffect ( ( ) => {
91104 setIsGlobalLoading ( isLoading || isSceneLoading || isCharacterLoading )
@@ -143,6 +156,8 @@ export default function Home() {
143156 *
144157 * Dispatches actions to update loading state and triggers a custom event
145158 * after a delay to ensure all components are initialized.
159+ *
160+ * @returns void
146161 */
147162 const handleCharacterLoaded = useCallback ( ( ) => {
148163 dispatch ( setIsCharacterLoading ( false ) )
@@ -159,6 +174,8 @@ export default function Home() {
159174 * Handle scene change.
160175 *
161176 * @param scene The name of the scene to switch to.
177+ *
178+ * @returns void
162179 */
163180 const handleSceneChange = ( scene : string ) => {
164181 dispatch ( setIsSceneLoading ( true ) )
@@ -173,6 +190,8 @@ export default function Home() {
173190 * Handle scene loaded event.
174191 *
175192 * Dispatches actions to update loading state when the scene has finished loading.
193+ *
194+ * @returns void
176195 */
177196 const handleSceneLoaded = useCallback ( ( ) => {
178197 dispatch ( setIsSceneLoading ( false ) )
@@ -185,6 +204,8 @@ export default function Home() {
185204 * Validates prerequisites, saves camera and scene state, captures a screenshot,
186205 * and opens a new window with the chat interface. Handles TTS compatibility checks
187206 * and location-based warnings for specific server hosts.
207+ *
208+ * @returns Promise<void> Resolves after the new window is opened or a notice is shown.
188209 */
189210 const handleStartConversation = useCallback ( async ( ) => {
190211 if ( isCharacterLoading || isSceneLoading ) {
@@ -231,6 +252,7 @@ export default function Home() {
231252 setLocationDialogOpen ( true )
232253 return
233254 }
255+
234256 try {
235257 dispatch ( setIsChatStarting ( true ) )
236258 if ( typeof window !== 'undefined' ) {
@@ -242,6 +264,24 @@ export default function Home() {
242264 await loadUserCharacters ( )
243265 }
244266 await new Promise ( r => setTimeout ( r , 800 ) )
267+ const missingSecret = await fetchGetMissingSecret (
268+ user ! . id ,
269+ selectedCharacterId ! ,
270+ )
271+ if (
272+ missingSecret . llm_requirements . length > 0 ||
273+ missingSecret . tts_requirements . length > 0 ||
274+ missingSecret . asr_requirements . length > 0
275+ ) {
276+ const data = {
277+ llm_requirements : missingSecret . llm_requirements . sort ( ) ,
278+ tts_requirements : missingSecret . tts_requirements . sort ( ) ,
279+ asr_requirements : missingSecret . asr_requirements . sort ( ) ,
280+ }
281+ setMissingSecret ( data )
282+ setMissingSecretDialogOpen ( true )
283+ return
284+ }
245285
246286 if ( babylonViewerRef . current ?. takeScreenshot ) {
247287 const screenshotData = await captureScreenshot ( )
@@ -312,6 +352,14 @@ export default function Home() {
312352 window . removeEventListener ( 'popstate' , handleRouteChange )
313353 }
314354 } , [ dispatch ] )
355+ /**
356+ * Chat action button.
357+ *
358+ * Renders the primary CTA to start a conversation, disabled when prerequisites
359+ * are not met.
360+ *
361+ * @returns JSX.Element | null The button or null when hidden on mobile during startup.
362+ */
315363 const ChatButton = useCallback ( ( ) => {
316364 if ( isChatStarting || ( isMobile && isLogin ) ) return null
317365 return (
@@ -347,6 +395,77 @@ export default function Home() {
347395 isCharacterLoading ,
348396 isSceneLoading ,
349397 ] )
398+ /**
399+ * Missing secret requirements renderer.
400+ *
401+ * Displays lists of required secrets for LLM/TTS/ASR providers if any are missing.
402+ *
403+ * @returns JSX.Element The message content for the dialog.
404+ */
405+ const MissingSecretMessage = ( ) => {
406+ return (
407+ < >
408+ { missingSecret . llm_requirements . length > 0 && (
409+ < div >
410+ { t ( 'missingSecret.message_llm' ) }
411+ < div
412+ style = { {
413+ padding : '20px 10px 20px 30px' ,
414+ display : 'flex' ,
415+ flexDirection : 'column' ,
416+ gap : '5px' ,
417+ } }
418+ >
419+ { missingSecret . llm_requirements . map ( requirement => (
420+ < div key = { requirement } >
421+ < span style = { { fontWeight : 'bold' } } > •</ span > { requirement }
422+ </ div >
423+ ) ) }
424+ </ div >
425+ </ div >
426+ ) }
427+ { missingSecret . tts_requirements . length > 0 && (
428+ < div >
429+ { t ( 'missingSecret.message_tts' ) }
430+ < div
431+ style = { {
432+ padding : '20px 10px 20px 30px' ,
433+ display : 'flex' ,
434+ flexDirection : 'column' ,
435+ gap : '5px' ,
436+ } }
437+ >
438+ { missingSecret . tts_requirements . map ( requirement => (
439+ < div key = { requirement } >
440+ < span style = { { fontWeight : 'bold' } } > •</ span > { requirement }
441+ </ div >
442+ ) ) }
443+ </ div >
444+ </ div >
445+ ) }
446+
447+ { missingSecret . asr_requirements . length > 0 && (
448+ < div >
449+ { t ( 'missingSecret.message_asr' ) }
450+ < div
451+ style = { {
452+ padding : '20px 10px 20px 30px' ,
453+ display : 'flex' ,
454+ flexDirection : 'column' ,
455+ gap : '5px' ,
456+ } }
457+ >
458+ { missingSecret . asr_requirements . map ( requirement => (
459+ < div key = { requirement } >
460+ < span style = { { fontWeight : 'bold' } } > •</ span > { requirement }
461+ </ div >
462+ ) ) }
463+ </ div >
464+ </ div >
465+ ) }
466+ </ >
467+ )
468+ }
350469
351470 return (
352471 < >
@@ -458,6 +577,20 @@ export default function Home() {
458577 message = { t ( 'networkLatencyWarning.message' ) }
459578 confirmText = { t ( 'networkLatencyWarning.confirmText' ) }
460579 />
580+ { /* Missing Secret Dialog */ }
581+ < ConfirmDialog
582+ isOpen = { missingSecretDialogOpen }
583+ onClose = { ( ) => {
584+ setMissingSecretDialogOpen ( false )
585+ } }
586+ showCloseButton = { false }
587+ showCancelButton = { false }
588+ message = { < MissingSecretMessage /> }
589+ confirmText = { t ( 'missingSecret.confirmText' ) }
590+ onConfirm = { ( ) => {
591+ setMissingSecretDialogOpen ( false )
592+ } }
593+ />
461594 { /* Footer */ }
462595 { ! isChatStarting && ! isLogin && (
463596 < footer className = { uiFadeOut ? 'fade-out' : '' } >
0 commit comments