22
33import { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
44import { useMutation , useQuery , useQueryClient } from '@tanstack/react-query' ;
5+ import Link from 'next/link' ;
56import { useRouter } from 'next/navigation' ;
67import { useAtom , useSetAtom } from 'jotai' ;
78import { toast } from 'sonner' ;
@@ -89,6 +90,12 @@ import {
8990 setLastUsedRepo ,
9091 setLastUsedVariant ,
9192} from '@/components/cloud-agent-next/model-preferences' ;
93+ import {
94+ GITHUB_IDENTITY_HINT_DISMISSED_STORAGE_KEY ,
95+ getGitHubIdentityHint ,
96+ getGitHubIdentityHintDismissed ,
97+ markGitHubIdentityHintDismissed ,
98+ } from '@/components/cloud-agent-next/github-identity-hint' ;
9299
93100type Repository = {
94101 id : number ;
@@ -102,6 +109,13 @@ type NewSessionPanelProps = {
102109 isDevcontainerAvailable : boolean ;
103110} ;
104111
112+ type ContextualTipProps = {
113+ body : string ;
114+ linkLabel : string ;
115+ href : string ;
116+ onDismiss : ( ) => void ;
117+ } ;
118+
105119export function NewSessionPanel ( { organizationId, isDevcontainerAvailable } : NewSessionPanelProps ) {
106120 const router = useRouter ( ) ;
107121 const trpc = useTRPC ( ) ;
@@ -111,6 +125,9 @@ export function NewSessionPanel({ organizationId, isDevcontainerAvailable }: New
111125 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
112126 const commandListRef = useRef < HTMLDivElement > ( null ) ;
113127 const [ devcontainer , setDevcontainer ] = useState ( false ) ;
128+ const [ isGitHubIdentityHintDismissed , setIsGitHubIdentityHintDismissed ] = useState <
129+ boolean | null
130+ > ( null ) ;
114131 const { mutateAsync : personalUploadUrl } = useMutation (
115132 trpc . cloudAgentNext . getAttachmentUploadUrl . mutationOptions ( )
116133 ) ;
@@ -173,6 +190,21 @@ export function NewSessionPanel({ organizationId, isDevcontainerAvailable }: New
173190 const [ isPreparing , setIsPreparing ] = useState ( false ) ;
174191 const [ attachmentMessageUuid , setAttachmentMessageUuid ] = useState ( ( ) => crypto . randomUUID ( ) ) ;
175192
193+ // ---------------------------------------------------------------------------
194+ // GitHub identity awareness
195+ // ---------------------------------------------------------------------------
196+ const {
197+ data : githubUserAuthorization ,
198+ isLoading : isGitHubUserAuthorizationLoading ,
199+ isError : isGitHubUserAuthorizationError ,
200+ } = useQuery ( {
201+ ...trpc . githubApps . getUserAuthorization . queryOptions ( ) ,
202+ enabled :
203+ isGitHubIdentityHintDismissed === false &&
204+ selectedRepo . length > 0 &&
205+ selectedPlatform === 'github' ,
206+ } ) ;
207+
176208 const attachmentUpload = useCloudAgentAttachmentUpload ( {
177209 messageUuid : attachmentMessageUuid ,
178210 organizationId,
@@ -197,6 +229,15 @@ export function NewSessionPanel({ organizationId, isDevcontainerAvailable }: New
197229
198230 useEffect ( ( ) => {
199231 setDevcontainer ( getDevcontainerEnabled ( ) ) ;
232+ setIsGitHubIdentityHintDismissed ( getGitHubIdentityHintDismissed ( ) ) ;
233+
234+ const handleGitHubIdentityHintStorage = ( event : StorageEvent ) => {
235+ if ( event . key === GITHUB_IDENTITY_HINT_DISMISSED_STORAGE_KEY && event . newValue === 'true' ) {
236+ setIsGitHubIdentityHintDismissed ( true ) ;
237+ }
238+ } ;
239+ window . addEventListener ( 'storage' , handleGitHubIdentityHintStorage ) ;
240+ return ( ) => window . removeEventListener ( 'storage' , handleGitHubIdentityHintStorage ) ;
200241 } , [ ] ) ;
201242
202243 const handleDevcontainerChange = useCallback ( ( enabled : boolean ) => {
@@ -865,6 +906,20 @@ export function NewSessionPanel({ organizationId, isDevcontainerAvailable }: New
865906 [ handleAutocompleteKeyDown , isFormValid , handleStartSession ]
866907 ) ;
867908
909+ const githubIdentityHint = getGitHubIdentityHint ( {
910+ selectedRepo,
911+ selectedPlatform,
912+ authorization : githubUserAuthorization ,
913+ isLoading : isGitHubUserAuthorizationLoading ,
914+ isError : isGitHubUserAuthorizationError ,
915+ isDismissed : isGitHubIdentityHintDismissed !== false ,
916+ } ) ;
917+
918+ const handleDismissGitHubIdentityHint = useCallback ( ( ) => {
919+ markGitHubIdentityHintDismissed ( ) ;
920+ setIsGitHubIdentityHintDismissed ( true ) ;
921+ } , [ ] ) ;
922+
868923 // ---------------------------------------------------------------------------
869924 // Integration missing view
870925 // ---------------------------------------------------------------------------
@@ -1326,6 +1381,42 @@ export function NewSessionPanel({ organizationId, isDevcontainerAvailable }: New
13261381 />
13271382 </ div >
13281383 </ div >
1384+
1385+ { githubIdentityHint && (
1386+ < ContextualTip { ...githubIdentityHint } onDismiss = { handleDismissGitHubIdentityHint } />
1387+ ) }
1388+ </ div >
1389+ </ div >
1390+ ) ;
1391+ }
1392+
1393+ function ContextualTip ( { body, linkLabel, href, onDismiss } : ContextualTipProps ) {
1394+ return (
1395+ < div className = "group/tip flex max-w-full justify-center text-center" role = "status" >
1396+ < div className = "text-muted-foreground inline-flex max-w-full items-start justify-center gap-1 text-xs" >
1397+ < span aria-hidden = "true" className = "invisible mr-1 shrink-0 px-1" >
1398+ Dismiss
1399+ </ span >
1400+ < span className = "text-foreground font-medium" > Tip:</ span >
1401+ < span aria-hidden = "true" className = "text-border" >
1402+ ·
1403+ </ span >
1404+ < span className = "min-w-0" >
1405+ { body } { ' ' }
1406+ < Link
1407+ href = { href }
1408+ className = "text-blue-400 hover:text-blue-300 hover:underline focus-visible:underline"
1409+ >
1410+ { linkLabel }
1411+ </ Link >
1412+ </ span >
1413+ < button
1414+ type = "button"
1415+ className = "text-muted-foreground/70 hover:text-foreground focus-visible:text-foreground focus-visible:ring-ring pointer-events-none -my-4 ml-1 shrink-0 cursor-pointer rounded-sm px-1 py-4 underline decoration-border underline-offset-4 opacity-0 transition-opacity group-focus-within/tip:pointer-events-auto group-focus-within/tip:opacity-100 group-hover/tip:pointer-events-auto group-hover/tip:opacity-100 focus-visible:ring-1 focus-visible:outline-none [@media(any-pointer:coarse)]:pointer-events-auto [@media(any-pointer:coarse)]:opacity-100 [@media(hover:none)]:pointer-events-auto [@media(hover:none)]:opacity-100"
1416+ onClick = { onDismiss }
1417+ >
1418+ Dismiss
1419+ </ button >
13291420 </ div >
13301421 </ div >
13311422 ) ;
@@ -1334,7 +1425,6 @@ export function NewSessionPanel({ organizationId, isDevcontainerAvailable }: New
13341425// ---------------------------------------------------------------------------
13351426// Internal sub-component for repo items in the Command list
13361427// ---------------------------------------------------------------------------
1337-
13381428function RepoCommandItem ( {
13391429 repo,
13401430 isSelected,
0 commit comments