@@ -4,8 +4,9 @@ import { AxiosError } from 'axios'
44import { Button , Label , Modal , Textarea , TextInput } from 'flowbite-react'
55import { useRouter } from 'next/router'
66import { useForm } from 'react-hook-form'
7- import { HiPlus } from 'react-icons/hi'
7+ import { HiPlus , HiDownload } from 'react-icons/hi'
88import { toast } from 'react-toastify'
9+ import { useState } from 'react'
910import {
1011 getListNodesForPublisherV2QueryKey ,
1112 Node ,
@@ -47,6 +48,84 @@ const adminCreateNodeDefaultValues: Partial<
4748 license : '{file="LICENSE"}' ,
4849}
4950
51+ interface PyProjectData {
52+ name ?: string
53+ description ?: string
54+ author ?: string
55+ license ?: string
56+ }
57+
58+ async function fetchGitHubRepoInfo (
59+ repoUrl : string
60+ ) : Promise < PyProjectData | null > {
61+ try {
62+ // Parse GitHub URL to extract owner and repo
63+ const urlMatch = repoUrl . match (
64+ / g i t h u b \. c o m \/ ( [ ^ \/ ] + ) \/ ( [ ^ \/ ] + ?) (?: \. g i t ) ? (?: \/ | $ ) /
65+ )
66+ if ( ! urlMatch ) {
67+ throw new Error ( 'Invalid GitHub URL format' )
68+ }
69+
70+ const [ , owner , repo ] = urlMatch
71+ const apiUrl = `https://api.github.com/repos/${ owner } /${ repo } /contents/pyproject.toml`
72+
73+ const response = await fetch ( apiUrl )
74+ if ( ! response . ok ) {
75+ if ( response . status === 404 ) {
76+ throw new Error ( 'pyproject.toml not found in repository' )
77+ }
78+ throw new Error ( `GitHub API error: ${ response . statusText } ` )
79+ }
80+
81+ const data = await response . json ( )
82+ const content = atob ( data . content )
83+
84+ // Basic TOML parsing for pyproject.toml
85+ const projectSection =
86+ content . match ( / \[ p r o j e c t \] ( [ \s \S ] * ?) (? = \n \[ | \n $ | $ ) / ) ?. [ 1 ] || ''
87+
88+ const result : PyProjectData = { }
89+
90+ // Extract name
91+ const nameMatch = projectSection . match ( / n a m e \s * = \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / )
92+ if ( nameMatch ) result . name = nameMatch [ 1 ]
93+
94+ // Extract description
95+ const descMatch = projectSection . match (
96+ / d e s c r i p t i o n \s * = \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] /
97+ )
98+ if ( descMatch ) result . description = descMatch [ 1 ]
99+
100+ // Extract author (from authors array or single author field)
101+ const authorsMatch = projectSection . match (
102+ / a u t h o r s \s * = \s * \[ ( [ \s \S ] * ?) \] /
103+ )
104+ if ( authorsMatch ) {
105+ const authorNameMatch = authorsMatch [ 1 ] . match (
106+ / n a m e \s * = \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] /
107+ )
108+ if ( authorNameMatch ) result . author = authorNameMatch [ 1 ]
109+ } else {
110+ const authorMatch = projectSection . match (
111+ / a u t h o r \s * = \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] /
112+ )
113+ if ( authorMatch ) result . author = authorMatch [ 1 ]
114+ }
115+
116+ // Extract license
117+ const licenseMatch =
118+ projectSection . match ( / l i c e n s e \s * = \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / ) ||
119+ content . match ( / l i c e n s e \s * = \s * \{ \s * t e x t \s * = \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / )
120+ if ( licenseMatch ) result . license = licenseMatch [ 1 ]
121+
122+ return result
123+ } catch ( error ) {
124+ console . error ( 'Error fetching GitHub repo info:' , error )
125+ throw error
126+ }
127+ }
128+
50129export function AdminCreateNodeFormModal ( {
51130 open,
52131 onClose,
@@ -56,6 +135,8 @@ export function AdminCreateNodeFormModal({
56135} ) {
57136 const { t } = useNextTranslation ( )
58137 const qc = useQueryClient ( )
138+ const [ isFetching , setIsFetching ] = useState ( false )
139+
59140 const mutation = useAdminCreateNode ( {
60141 mutation : {
61142 onError : ( error ) => {
@@ -83,6 +164,7 @@ export function AdminCreateNodeFormModal({
83164 formState : { errors } ,
84165 watch,
85166 reset,
167+ setValue,
86168 } = useForm < Node > ( {
87169 resolver : zodResolver ( adminCreateNodeSchema ) as any ,
88170 defaultValues : adminCreateNodeDefaultValues ,
@@ -106,6 +188,38 @@ export function AdminCreateNodeFormModal({
106188 } )
107189 } )
108190
191+ const handleFetchRepoInfo = async ( ) => {
192+ const repository = watch ( 'repository' )
193+ if ( ! repository ) {
194+ toast . error ( t ( 'Please enter a repository URL first' ) )
195+ return
196+ }
197+
198+ setIsFetching ( true )
199+ try {
200+ const repoData = await fetchGitHubRepoInfo ( repository )
201+ if ( repoData ) {
202+ if ( repoData . name ) setValue ( 'name' , repoData . name )
203+ if ( repoData . description )
204+ setValue ( 'description' , repoData . description )
205+ if ( repoData . author ) setValue ( 'author' , repoData . author )
206+ if ( repoData . license ) setValue ( 'license' , repoData . license )
207+
208+ toast . success ( t ( 'Repository information fetched successfully' ) )
209+ }
210+ } catch ( error ) {
211+ const errorMessage =
212+ error instanceof Error ? error . message : 'Unknown error'
213+ toast . error (
214+ t ( 'Failed to fetch repository information: {{error}}' , {
215+ error : errorMessage ,
216+ } )
217+ )
218+ } finally {
219+ setIsFetching ( false )
220+ }
221+ }
222+
109223 const { data : allPublishers } = useListPublishers ( {
110224 query : { enabled : false } ,
111225 } ) // list publishers for unclaimed user
@@ -146,6 +260,40 @@ export function AdminCreateNodeFormModal({
146260 >
147261 < p className = "text-white" > { t ( 'Add unclaimed node' ) } </ p >
148262
263+ < div >
264+ < Label htmlFor = "repository" >
265+ { t ( 'Repository URL' ) }
266+ </ Label >
267+ < div className = "flex gap-2" >
268+ < TextInput
269+ id = "repository"
270+ { ...register ( 'repository' ) }
271+ placeholder = "https://github.com/user/repo"
272+ className = "flex-1"
273+ />
274+ < Button
275+ type = "button"
276+ size = "sm"
277+ onClick = { handleFetchRepoInfo }
278+ disabled = { isFetching }
279+ className = "whitespace-nowrap"
280+ >
281+ < HiDownload className = "mr-2 h-4 w-4" />
282+ { isFetching
283+ ? t ( 'Fetching...' )
284+ : t ( 'Fetch Info' ) }
285+ </ Button >
286+ </ div >
287+ < span className = "text-error" >
288+ { errors . repository ?. message }
289+ </ span >
290+ < p className = "text-xs text-gray-400 mt-1" >
291+ { t (
292+ 'Enter a GitHub repository URL and click "Fetch Info" to automatically fill in details from pyproject.toml'
293+ ) }
294+ </ p >
295+ </ div >
296+
149297 < div >
150298 < Label htmlFor = "id" > { t ( 'ID' ) } </ Label >
151299 < TextInput id = "id" { ...register ( 'id' ) } />
@@ -222,17 +370,6 @@ export function AdminCreateNodeFormModal({
222370 </ span >
223371 </ div >
224372
225- < div >
226- < Label htmlFor = "repository" > { t ( 'Repository' ) } </ Label >
227- < TextInput
228- id = "repository"
229- { ...register ( 'repository' ) }
230- />
231- < span className = "text-error" >
232- { errors . repository ?. message }
233- </ span >
234- </ div >
235-
236373 < div >
237374 < Label htmlFor = "license" > { t ( 'License' ) } </ Label >
238375 < TextInput id = "license" { ...register ( 'license' ) } />
0 commit comments