@@ -4,6 +4,17 @@ import { FC, useState, useEffect } from "react";
44import InitialInputArea from "./InitialInputArea" ;
55import { suggestions } from "@/utils/utils" ;
66import { strategies } from "@/utils/strategies" ;
7+ import { loadClientSettings } from "@/utils/settings" ;
8+
9+ const PROVIDER_LABELS : Record < string , string > = {
10+ ollama : "Ollama" ,
11+ openai : "OpenAI" ,
12+ anthropic : "Anthropic" ,
13+ google : "Google" ,
14+ groq : "Groq" ,
15+ openrouter : "OpenRouter" ,
16+ together : "Together AI" ,
17+ } ;
718
819type THeroProps = {
920 promptValue : string ;
@@ -40,6 +51,7 @@ const Hero: FC<THeroProps> = ({
4051} ) => {
4152 const [ showNotes , setShowNotes ] = useState ( false ) ;
4253 const [ showFirstRun , setShowFirstRun ] = useState ( false ) ;
54+ const [ providerWarning , setProviderWarning ] = useState < string | null > ( null ) ;
4355
4456 useEffect ( ( ) => {
4557 const dismissed = localStorage . getItem ( "studybuddy-first-run-dismissed" ) ;
@@ -48,6 +60,43 @@ const Hero: FC<THeroProps> = ({
4860 }
4961 } , [ ] ) ;
5062
63+ // Check the AI provider is reachable before the user invests in a session.
64+ useEffect ( ( ) => {
65+ let cancelled = false ;
66+
67+ const check = async ( ) => {
68+ const settings = loadClientSettings ( ) ;
69+ const label = PROVIDER_LABELS [ settings . llmProvider ] || settings . llmProvider ;
70+ const warning =
71+ settings . llmProvider === "ollama"
72+ ? "I can't reach Ollama right now. Make sure it's running on your computer, or pick another provider in Settings."
73+ : `I can't reach ${ label } right now. Check your secret key and connection in Settings.` ;
74+ try {
75+ const res = await fetch ( "/api/models" , {
76+ method : "POST" ,
77+ headers : { "Content-Type" : "application/json" } ,
78+ body : JSON . stringify ( {
79+ provider : settings . llmProvider ,
80+ baseUrl : settings . llmBaseUrl || undefined ,
81+ apiKey : settings . llmApiKey || undefined ,
82+ } ) ,
83+ } ) ;
84+ if ( ! cancelled ) setProviderWarning ( res . ok ? null : warning ) ;
85+ } catch {
86+ if ( ! cancelled ) setProviderWarning ( warning ) ;
87+ }
88+ } ;
89+
90+ check ( ) ;
91+ window . addEventListener ( "settingsChanged" , check ) ;
92+ window . addEventListener ( "settingsLoaded" , check ) ;
93+ return ( ) => {
94+ cancelled = true ;
95+ window . removeEventListener ( "settingsChanged" , check ) ;
96+ window . removeEventListener ( "settingsLoaded" , check ) ;
97+ } ;
98+ } , [ ] ) ;
99+
51100 const dismissFirstRun = ( ) => {
52101 setShowFirstRun ( false ) ;
53102 localStorage . setItem ( "studybuddy-first-run-dismissed" , "true" ) ;
@@ -99,6 +148,21 @@ const Hero: FC<THeroProps> = ({
99148 </ div >
100149 ) }
101150
151+ { /* Provider connectivity warning (hidden while the first-run banner covers setup) */ }
152+ { ! showFirstRun && providerWarning && (
153+ < div className = "mb-4 w-full rounded-soft border border-hairline-strong bg-paper-warm px-4 py-3" >
154+ < p className = "text-sm text-ink-muted" style = { { lineHeight : 1.6 } } >
155+ { providerWarning } { " " }
156+ < Link
157+ href = "/settings"
158+ className = "font-medium text-ink underline transition-colors duration-normal hover:text-accent"
159+ >
160+ Open Settings
161+ </ Link >
162+ </ p >
163+ </ div >
164+ ) }
165+
102166 < h2 className = "mt-2 text-center text-4xl font-semibold tracking-tight text-ink sm:text-6xl" style = { { lineHeight : 1.1 } } >
103167 Your Personal{ " " }
104168 < span className = "text-accent" > Tutor</ span >
0 commit comments