66 DialogTitle ,
77 TextField ,
88} from '@mui/material'
9- import React , { useState } from 'react'
9+ import React , { useEffect , useRef , useState } from 'react'
1010import { useTranslation } from 'react-i18next'
1111
1212const API_BASE_URL = 'https://model.xinference.io'
@@ -15,11 +15,10 @@ const AddModelDialog = ({ open, onClose }) => {
1515 const { t } = useTranslation ( )
1616 const [ url , setUrl ] = useState ( '' )
1717 const [ loginOpen , setLoginOpen ] = useState ( false )
18- const [ usernameOrEmail , setUsernameOrEmail ] = useState ( '' )
19- const [ password , setPassword ] = useState ( '' )
2018 const [ pendingModelId , setPendingModelId ] = useState ( null )
2119 const [ loading , setLoading ] = useState ( false )
2220 const [ errorMsg , setErrorMsg ] = useState ( '' )
21+ const loginIframeRef = useRef ( null )
2322
2423 const handleClose = ( type ) => {
2524 setErrorMsg ( '' )
@@ -46,18 +45,62 @@ const AddModelDialog = ({ open, onClose }) => {
4645 return null
4746 }
4847
49- const performDownload = async ( modelId , token , fromLogin = false ) => {
48+ // 修改:download 默认从 sessionStorage 读取 token(若传参提供则优先)
49+ // performDownload:收到 token 后直连接口,获取 JSON
50+ const performDownload = async ( modelId , tokenFromParam , fromLogin = false ) => {
5051 const endpoint = `${ API_BASE_URL } /api/models/download?model_id=${ encodeURIComponent (
5152 modelId
5253 ) } `
53- const headers = token ? { Authorization : `Bearer ${ token } ` } : { }
54+ const effectiveToken =
55+ tokenFromParam ||
56+ sessionStorage . getItem ( 'model_hub_token' ) ||
57+ localStorage . getItem ( 'io_login_success' )
58+ const headers = effectiveToken ? { Authorization : `Bearer ${ effectiveToken } ` } : { }
5459 setLoading ( true )
5560 setErrorMsg ( '' )
5661 try {
5762 const res = await fetch ( endpoint , {
5863 method : 'GET' ,
5964 headers,
6065 } )
66+
67+ if ( res . status === 401 ) {
68+ const refreshToken = sessionStorage . getItem ( 'model_hub_refresh_token' )
69+ if ( ! refreshToken ) {
70+ sessionStorage . removeItem ( 'model_hub_token' )
71+ setPendingModelId ( modelId )
72+ setLoginOpen ( true )
73+ return
74+ }
75+ try {
76+ const refreshRes = await fetch ( `${ API_BASE_URL } /api/users/refresh` , {
77+ method : 'POST' ,
78+ headers : { 'Content-Type' : 'application/json' } ,
79+ body : JSON . stringify ( { token : refreshToken } ) ,
80+ } )
81+ if ( ! refreshRes . ok ) {
82+ throw new Error ( `refresh failed: ${ refreshRes . status } ` )
83+ }
84+ const refreshData = await refreshRes . json ( ) . catch ( ( ) => ( { } ) )
85+ const newToken = refreshData ?. data ?. accessToken
86+ if ( newToken ) {
87+ sessionStorage . setItem ( 'model_hub_token' , newToken )
88+ await performDownload ( modelId , newToken , false )
89+ return
90+ } else {
91+ sessionStorage . removeItem ( 'model_hub_token' )
92+ setPendingModelId ( modelId )
93+ setLoginOpen ( true )
94+ return
95+ }
96+ } catch ( e ) {
97+ sessionStorage . removeItem ( 'model_hub_token' )
98+ setPendingModelId ( modelId )
99+ setLoginOpen ( true )
100+ return
101+ }
102+ }
103+
61104 if ( res . status === 403 ) {
62105 let detailMsg = ''
63106 try {
@@ -68,20 +111,19 @@ const AddModelDialog = ({ open, onClose }) => {
68111 detailMsg = body . message
69112 }
70113 } catch {
71- // ignore and use default message
114+ console . log ( '' ) ;
115+
72116 }
73-
74117 if ( fromLogin ) {
75- setErrorMsg (
76- detailMsg || t ( 'launchModel.error.noPermissionAfterLogin' )
77- )
118+ setErrorMsg ( detailMsg || t ( 'launchModel.error.noPermissionAfterLogin' ) )
78119 return
79120 } else {
80121 setPendingModelId ( modelId )
81122 setLoginOpen ( true )
82123 return
83124 }
84125 }
126+
85127 if ( ! res . ok ) {
86128 const text = await res . text ( ) . catch ( ( ) => '' )
87129 throw new Error (
@@ -109,43 +151,26 @@ const AddModelDialog = ({ open, onClose }) => {
109151 await performDownload ( modelId )
110152 }
111153
112- const handleLoginSubmit = async ( e ) => {
113- e . preventDefault ( )
114- if ( ! pendingModelId ) return
115- setLoading ( true )
116- setErrorMsg ( '' )
117- try {
118- const loginRes = await fetch ( `${ API_BASE_URL } /api/users/login` , {
119- method : 'POST' ,
120- headers : { 'Content-Type' : 'application/json' } ,
121- body : JSON . stringify ( {
122- usernameOrEmail : usernameOrEmail . trim ( ) ,
123- password : password ,
124- } ) ,
125- } )
126- if ( ! loginRes . ok ) {
127- const text = await loginRes . text ( ) . catch ( ( ) => '' )
128- throw new Error (
129- t ( 'launchModel.error.loginFailedText' , {
130- status : loginRes . status ,
131- text,
132- } )
133- )
134- }
135- const loginJson = await loginRes . json ( )
136- const token = loginJson . data ?. accessToken
137- if ( ! token ) {
138- throw new Error ( t ( 'launchModel.error.noTokenAfterLogin' ) )
154+ useEffect ( ( ) => {
155+ const listener = ( event ) => {
156+ if ( event . origin !== API_BASE_URL ) return
157+ const { type, token, refresh_token } = event . data || { }
158+
159+ if ( type === 'io_login_success' && token && refresh_token ) {
160+ handleClose ( 'login' )
161+ sessionStorage . setItem ( 'model_hub_token' , token )
162+ sessionStorage . setItem ( 'model_hub_refresh_token' , refresh_token )
163+ if ( pendingModelId ) {
164+ void performDownload ( pendingModelId , token , true )
165+ }
139166 }
140- handleClose ( 'login' )
141- await performDownload ( pendingModelId , token , true )
142- } catch ( err ) {
143- console . error ( err )
144- setErrorMsg ( err . message || t ( 'launchModel.error.requestFailed' ) )
145- } finally {
146- setLoading ( false )
147167 }
148- }
168+
169+ window . addEventListener ( 'message' , listener )
170+ return ( ) => {
171+ window . removeEventListener ( 'message' , listener )
172+ }
173+ } , [ pendingModelId ] )
149174
150175 return (
151176 < Dialog open = { open } onClose = { ( ) => handleClose ( 'add' ) } width = { 500 } >
@@ -163,7 +188,7 @@ const AddModelDialog = ({ open, onClose }) => {
163188 < div >
164189 { t ( 'launchModel.addModelDialog.introPrefix' ) } { ' ' }
165190 < a
166- href = "https://model.xinference.io/ models"
191+ href = { ` ${ API_BASE_URL } / models` }
167192 target = "_blank"
168193 rel = "noopener noreferrer"
169194 style = { { textDecoration : 'none' , color : '#1976d2' } }
@@ -212,55 +237,27 @@ const AddModelDialog = ({ open, onClose }) => {
212237 </ Button >
213238 </ DialogActions >
214239
215- { /* 403 */ }
216240 < Dialog open = { loginOpen } onClose = { ( ) => handleClose ( 'login' ) } >
217- < DialogTitle > { t ( 'launchModel.loginDialog.title' ) } </ DialogTitle >
218- < DialogContent >
219- < form
220- onSubmit = { handleLoginSubmit }
221- id = "login-form"
222- style = { {
223- display : 'flex' ,
224- flexDirection : 'column' ,
225- gap : 12 ,
226- width : 360 ,
227- paddingTop : 10 ,
228- } }
229- >
230- < TextField
231- required
232- id = "usernameOrEmail"
233- name = "usernameOrEmail"
234- label = { t ( 'launchModel.loginDialog.usernameOrEmail' ) }
235- fullWidth
236- value = { usernameOrEmail }
237- onChange = { ( e ) => setUsernameOrEmail ( e . target . value ) }
238- disabled = { loading }
239- />
240- < TextField
241- required
242- id = "password"
243- name = "password"
244- label = { t ( 'launchModel.loginDialog.password' ) }
245- type = "password"
246- fullWidth
247- value = { password }
248- onChange = { ( e ) => setPassword ( e . target . value ) }
249- disabled = { loading }
250- />
251- </ form >
252- { errorMsg && (
253- < div style = { { color : '#d32f2f' , marginTop : 8 } } > { errorMsg } </ div >
254- ) }
255- </ DialogContent >
256- < DialogActions >
257- < Button onClick = { ( ) => handleClose ( 'login' ) } disabled = { loading } >
258- { t ( 'launchModel.cancel' ) }
259- </ Button >
260- < Button type = "submit" form = "login-form" disabled = { loading } >
261- { t ( 'launchModel.loginDialog.login' ) }
262- </ Button >
263- </ DialogActions >
241+ < div
242+ style = { {
243+ width : '100%' ,
244+ maxWidth : 640 ,
245+ padding : 16 ,
246+ boxSizing : 'border-box' ,
247+ } }
248+ >
249+ < iframe
250+ ref = { loginIframeRef }
251+ src = { `${ API_BASE_URL } /signin` }
252+ title = "Model Platform Signin"
253+ style = { { width : '100%' , minHeight : 520 , border : 0 } }
254+ />
255+ < div style = { { display : 'flex' , justifyContent : 'flex-end' , marginTop : 12 } } >
256+ < Button onClick = { ( ) => handleClose ( 'login' ) } disabled = { loading } >
257+ 关闭
258+ </ Button >
259+ </ div >
260+ </ div >
264261 </ Dialog >
265262 </ Dialog >
266263 )
0 commit comments