360360 align-items : center;justify-content : center;backdrop-filter : blur (4px );
361361}
362362# key-modal .show {display : flex}
363- .km-box {background : var (--s1 );border : 1px solid var (--border );border-radius : var (--r );padding : 20 px ;max-width : 380 px ;width : 90% }
363+ .km-box {background : var (--s1 );border : 1px solid var (--border );border-radius : var (--r );padding : 24 px ;max-width : 440 px ;width : 90% }
364364.km-input {width : 100% ;background : var (--s2 );border : 1px solid var (--border );border-radius : 5px ;padding : 8px 12px ;font-family : var (--mono );font-size : 12px ;color : var (--text );outline : none;margin-bottom : 10px }
365365.km-input : focus {border-color : var (--acc )}
366366.km-btn {background : var (--acc );color : # fff ;border : none;border-radius : 5px ;padding : 8px 16px ;cursor : pointer;font-family : var (--font );font-size : 12px ;font-weight : 600 ;width : 100% }
388388 < button id ="analyze-btn " type ="button " onclick ="startAnalysis() "> Analyze</ button >
389389 </ form >
390390 < div class ="top-right ">
391- < div class ="key-badge " onclick ="showKeyModal() " title ="Set Groq API key for AI summaries ">
392- < span id ="key-indicator "> ⚡ AI Key </ span >
391+ < div class ="key-badge " onclick ="showKeyModal() " title ="Set GitHub token (fixes rate limits) + Groq AI key ">
392+ < span id ="key-indicator "> ⚡ Set Keys </ span >
393393 </ div >
394394 </ div >
395395</ div >
439439 </ div >
440440 < h1 class ="landing-title "> Repository Intelligence</ h1 >
441441 < p class ="landing-sub "> Paste any GitHub repository URL and instantly understand the entire codebase — dependency graph, architecture, health score, AI summaries and more.</ p >
442+ < div id ="landing-key-notice " style ="display:none;align-items:center;gap:8px;background:#0f0f1a;border:1px solid #f59e0b30;border-radius:8px;padding:10px 14px;font-size:11.5px;color:#f59e0b;margin-bottom:12px;max-width:480px;text-align:left ">
443+ < svg width ="14 " height ="14 " viewBox ="0 0 24 24 " fill ="none " style ="flex-shrink:0 "> < path d ="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z " stroke ="#f59e0b " stroke-width ="1.5 "/> < line x1 ="12 " y1 ="9 " x2 ="12 " y2 ="13 " stroke ="#f59e0b " stroke-width ="1.5 " stroke-linecap ="round "/> < circle cx ="12 " cy ="17 " r ="1 " fill ="#f59e0b "/> </ svg >
444+ < span > No GitHub token set — analysis may hit rate limits (60 req/hr). < button onclick ="showKeyModal() " style ="background:transparent;border:none;color:#f59e0b;text-decoration:underline;cursor:pointer;font-size:11.5px;padding:0 "> Add a token ↗</ button > for best results.</ span >
445+ </ div >
442446 < div class ="landing-examples ">
443447 < div class ="ex-btn " onclick ="loadExample('facebook/react') "> facebook/react</ div >
444448 < div class ="ex-btn " onclick ="loadExample('vercel/next.js') "> vercel/next.js</ div >
@@ -541,24 +545,27 @@ <h1 class="landing-title">Repository Intelligence</h1>
541545<!-- KEY MODAL -->
542546< div id ="key-modal " onclick ="if(event.target===this)hideKeyModal() ">
543547 < div class ="km-box ">
544- < div style ="font-size:14px;font-weight:600;margin-bottom:4px "> Groq API Key</ div >
545- < div style ="font-size:11px;color:var(--t2);margin-bottom:12px;line-height:1.6 "> Required for AI summaries. Get a free key at < a href ="https://console.groq.com " target ="_blank "> console.groq.com</ a > </ div >
548+ < div style ="font-size:14px;font-weight:600;margin-bottom:16px "> API Keys</ div >
549+
550+ < div style ="font-size:11px;font-weight:600;color:var(--t2);margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px "> GitHub Token < span style ="color:var(--ylw) "> (recommended)</ span > </ div >
551+ < div style ="font-size:11px;color:var(--t3);margin-bottom:8px;line-height:1.6 "> Avoids rate limits (60 req/hr without token → 5000/hr with token). < a href ="https://github.com/settings/tokens/new?scopes=public_repo&description=Devora+Repo+Intel " target ="_blank " style ="color:var(--acc2) "> Generate a token ↗</ a > — only needs < code style ="font-size:10px;background:var(--s3);padding:1px 4px;border-radius:3px "> public_repo</ code > scope.</ div >
552+ < input class ="km-input " id ="km-gh-input " type ="password " placeholder ="ghp_xxxx or github_pat_xxxx " style ="margin-bottom:16px ">
553+
554+ < div style ="font-size:11px;font-weight:600;color:var(--t2);margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px "> Groq API Key < span style ="color:var(--t3) "> (optional)</ span > </ div >
555+ < div style ="font-size:11px;color:var(--t3);margin-bottom:8px;line-height:1.6 "> Powers AI summaries & explanations. Get a free key at < a href ="https://console.groq.com " target ="_blank " style ="color:var(--acc2) "> console.groq.com ↗</ a > </ div >
546556 < input class ="km-input " id ="km-key-input " type ="password " placeholder ="gsk_xxxx ">
547- < button class ="km-btn " onclick ="saveKey() "> Save Key</ button >
557+
558+ < button class ="km-btn " onclick ="saveKey() "> Save Keys</ button >
548559 </ div >
549560</ div >
550561
551562< script >
552563// ── CONFIG ──────────────────────────────────────────────────────────────
553- const GH_TOKEN = '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' ;
554- const GROQ_KEYS = [
555- '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' ,
556- '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' ,
557- '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' ,
558- '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' ,
559- ] ;
564+ // NOTE: Never hardcode API tokens — they get auto-revoked on public repos.
565+ // Tokens are provided by the user and stored in localStorage only.
560566const GROQ_MODELS = [ 'llama-3.3-70b-versatile' , 'llama-3.1-8b-instant' , 'gemma2-9b-it' ] ;
561567let _groqKey = localStorage . getItem ( 'ri_groq' ) || '' ;
568+ let _ghToken = localStorage . getItem ( 'ri_gh' ) || '' ;
562569let _gki = 0 ;
563570
564571// ── STATE ───────────────────────────────────────────────────────────────
@@ -580,22 +587,51 @@ <h1 class="landing-title">Repository Intelligence</h1>
580587} ;
581588
582589// ── KEY MANAGEMENT ──────────────────────────────────────────────────────
583- function showKeyModal ( ) { document . getElementById ( 'key-modal' ) . classList . add ( 'show' ) ; document . getElementById ( 'km-key-input' ) . value = _groqKey ; }
590+ function _updateKeyBadge ( ) {
591+ const hasGh = ! ! _ghToken , hasGroq = ! ! _groqKey ;
592+ document . getElementById ( 'key-indicator' ) . textContent =
593+ hasGh && hasGroq ?'✓ GH + AI' :hasGh ?'✓ GH Token' :hasGroq ?'✓ AI Key' :'⚡ Set Keys' ;
594+ }
595+ function showKeyModal ( ) {
596+ document . getElementById ( 'key-modal' ) . classList . add ( 'show' ) ;
597+ document . getElementById ( 'km-key-input' ) . value = _groqKey ;
598+ document . getElementById ( 'km-gh-input' ) . value = _ghToken ;
599+ }
584600function hideKeyModal ( ) { document . getElementById ( 'key-modal' ) . classList . remove ( 'show' ) ; }
585- function saveKey ( ) { _groqKey = document . getElementById ( 'km-key-input' ) . value . trim ( ) ; localStorage . setItem ( 'ri_groq' , _groqKey ) ; document . getElementById ( 'key-indicator' ) . textContent = _groqKey ?'✓ Key Set' :'⚡ AI Key' ; hideKeyModal ( ) ; }
586- if ( _groqKey ) document . getElementById ( 'key-indicator' ) . textContent = '✓ Key Set' ;
601+ function saveKey ( ) {
602+ _groqKey = document . getElementById ( 'km-key-input' ) . value . trim ( ) ;
603+ _ghToken = document . getElementById ( 'km-gh-input' ) . value . trim ( ) ;
604+ localStorage . setItem ( 'ri_groq' , _groqKey ) ;
605+ if ( _ghToken ) localStorage . setItem ( 'ri_gh' , _ghToken ) ;
606+ else localStorage . removeItem ( 'ri_gh' ) ;
607+ _updateKeyBadge ( ) ;
608+ hideKeyModal ( ) ;
609+ }
610+ _updateKeyBadge ( ) ;
587611
588612// ── GITHUB API ──────────────────────────────────────────────────────────
589613async function ghFetch ( path ) {
590- const r = await fetch ( 'https://api.github.com' + path , { headers :{ 'Authorization' :'Bearer ' + GH_TOKEN , 'Accept' :'application/vnd.github.v3+json' } } ) ;
614+ const headers = { 'Accept' :'application/vnd.github.v3+json' } ;
615+ if ( _ghToken ) headers [ 'Authorization' ] = 'Bearer ' + _ghToken ;
616+ const r = await fetch ( 'https://api.github.com' + path , { headers} ) ;
617+ if ( r . status === 401 ) {
618+ // Token is invalid — clear it so user knows to re-enter
619+ _ghToken = '' ; localStorage . removeItem ( 'ri_gh' ) ; _updateKeyBadge ( ) ;
620+ throw new Error ( 'GitHub token invalid or expired. Click ⚡ Set Keys to add a new one.' ) ;
621+ }
622+ if ( r . status === 403 ) {
623+ const isRateLimit = ( await r . clone ( ) . json ( ) . catch ( ( ) => ( { } ) ) ) . message ?. includes ( 'rate limit' ) ;
624+ if ( isRateLimit && ! _ghToken ) throw new Error ( 'GitHub rate limit hit (60 req/hr for unauthenticated). Click ⚡ Set Keys to add a GitHub token for 5000 req/hr.' ) ;
625+ throw new Error ( 'GitHub API 403: ' + path ) ;
626+ }
591627 if ( ! r . ok ) throw new Error ( 'GitHub API ' + r . status + ': ' + path ) ;
592628 return r . json ( ) ;
593629}
594630async function ghSafe ( path ) { try { return await ghFetch ( path ) ; } catch { return null ; } }
595631
596632// ── GROQ AI ─────────────────────────────────────────────────────────────
597633async function callGroq ( prompt , maxTok = 600 ) {
598- const key = _groqKey || null ;
634+ if ( ! _groqKey ) return null ; // No key — AI features disabled, not an error
599635 const tryOne = async ( k , m ) => {
600636 try {
601637 const r = await fetch ( 'https://api.groq.com/openai/v1/chat/completions' , {
@@ -609,10 +645,7 @@ <h1 class="landing-title">Repository Intelligence</h1>
609645 return d . choices ?. [ 0 ] ?. message ?. content ?. trim ( ) || null ;
610646 } catch { return null ; }
611647 } ;
612- if ( key ) { for ( const m of GROQ_MODELS ) { const t = await tryOne ( key , m ) ; if ( t ) return t ; } }
613- for ( let i = _gki ; i < GROQ_KEYS . length ; i ++ ) {
614- for ( const m of GROQ_MODELS ) { const t = await tryOne ( GROQ_KEYS [ i ] , m ) ; if ( t ) { _gki = i ; return t ; } }
615- }
648+ for ( const m of GROQ_MODELS ) { const t = await tryOne ( _groqKey , m ) ; if ( t ) return t ; }
616649 return null ;
617650}
618651
@@ -630,23 +663,38 @@ <h1 class="landing-title">Repository Intelligence</h1>
630663async function startAnalysis ( ) {
631664 const input = document . getElementById ( 'url-input' ) . value ;
632665 const repo = parseRepoUrl ( input ) ;
633- if ( ! repo ) { alert ( 'Please enter a valid GitHub repo URL or owner/name ' ) ; return ; }
666+ if ( ! repo ) { showInlineError ( 'Please enter a valid GitHub repo URL (e.g. github.com/ owner/repo) ' ) ; return ; }
634667 S . repo = repo ;
635668 document . getElementById ( 'analyze-btn' ) . disabled = true ;
636669 document . getElementById ( 'analyze-btn' ) . textContent = 'Analyzing…' ;
670+ clearInlineError ( ) ;
637671
638672 showLoading ( ) ;
639673 try {
640674 await runAnalysis ( ) ;
641675 } catch ( e ) {
642676 console . error ( e ) ;
643- alert ( 'Error analyzing repository: ' + e . message ) ;
677+ const isAuthErr = e . message . includes ( 'token' ) || e . message . includes ( '401' ) || e . message . includes ( 'rate limit' ) || e . message . includes ( '403' ) ;
678+ showInlineError ( e . message , isAuthErr ) ;
644679 showLanding ( ) ;
645680 }
646681 document . getElementById ( 'analyze-btn' ) . disabled = false ;
647682 document . getElementById ( 'analyze-btn' ) . textContent = 'Analyze' ;
648683}
649684
685+ function showInlineError ( msg , showKeyBtn = false ) {
686+ let el = document . getElementById ( 'inline-error' ) ;
687+ if ( ! el ) {
688+ el = document . createElement ( 'div' ) ;
689+ el . id = 'inline-error' ;
690+ el . style . cssText = 'position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:999;background:#1a0f0f;border:1px solid #f8717140;border-radius:8px;padding:10px 16px;font-size:12px;color:#f87171;display:flex;align-items:center;gap:10px;max-width:520px;box-shadow:0 4px 24px #00000060;' ;
691+ document . body . appendChild ( el ) ;
692+ }
693+ el . innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="#f87171" stroke-width="1.5"/><line x1="12" y1="8" x2="12" y2="12" stroke="#f87171" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="16" r="1" fill="#f87171"/></svg><span style="flex:1">${ msg } </span>${ showKeyBtn ?`<button onclick="showKeyModal();clearInlineError()" style="background:#f8717118;border:1px solid #f8717140;color:#f87171;border-radius:5px;padding:4px 10px;font-size:11px;cursor:pointer;white-space:nowrap">⚡ Set Keys</button>` :'' } <button onclick="clearInlineError()" style="background:transparent;border:none;color:#f87171;cursor:pointer;font-size:16px;padding:0 0 0 4px">×</button>` ;
694+ el . style . display = 'flex' ;
695+ }
696+ function clearInlineError ( ) { const el = document . getElementById ( 'inline-error' ) ; if ( el ) el . style . display = 'none' ; }
697+
650698function step ( id , state ) {
651699 const el = document . getElementById ( id ) ;
652700 if ( ! el ) return ;
@@ -1699,7 +1747,19 @@ <h2 style="font-size:16px;font-weight:700;margin-bottom:6px;letter-spacing:-.3px
16991747
17001748// Init
17011749document . getElementById ( 'url-input' ) . addEventListener ( 'keydown' , e => { if ( e . key === 'Enter' ) startAnalysis ( ) ; } ) ;
1750+
1751+ // Show no-token warning on landing if user hasn't set a GH token
1752+ function showLandingKeyNotice ( ) {
1753+ const notice = document . getElementById ( 'landing-key-notice' ) ;
1754+ if ( notice ) notice . style . display = _ghToken ?'none' :'flex' ;
1755+ }
1756+
1757+ // Patch showKeyModal/saveKey to refresh the notice
1758+ const _origSave = saveKey ;
1759+ saveKey = function ( ) { _origSave ( ) ; showLandingKeyNotice ( ) ; } ;
1760+
17021761showLanding ( ) ;
1762+ showLandingKeyNotice ( ) ;
17031763</ script >
17041764</ body >
1705- </ html >
1765+ </ html >
0 commit comments