App.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import React, { useEffect, useState } from 'react'
  2. import { setDragDropEnabled } from './components/GlobalDragDropOverlay'
  3. import { setNotebookDragDropEnabled } from './components/NotebookGlobalDragDropOverlay'
  4. import LoginPage from './components/LoginPage'
  5. import { SidebarRail, ViewType } from './components/layouts/SidebarRail'
  6. import { ChatView } from './components/views/ChatView'
  7. import { KnowledgeBaseView } from './components/views/KnowledgeBaseView'
  8. import { NotebooksView } from './components/views/NotebooksView'
  9. import { SettingsView } from './components/views/SettingsView'
  10. import { LanguageProvider } from './contexts/LanguageContext'
  11. import { ToastProvider } from './contexts/ToastContext'
  12. import { modelConfigService } from './services/modelConfigService'
  13. import { authService } from './services/authService'
  14. import { ModelConfig, DEFAULT_MODELS } from './types'
  15. const AppContent: React.FC = () => {
  16. const [authToken, setAuthToken] = useState<string | null>(null)
  17. const [currentUser, setCurrentUser] = useState<any>(null); // Add current user state
  18. const [currentView, setCurrentView] = useState<ViewType>('chat')
  19. const [isVerifying, setIsVerifying] = useState(true)
  20. // Chat Context State
  21. const [chatContext, setChatContext] = useState<{ selectedGroups?: string[], selectedFiles?: string[] } | null>(null)
  22. // Model State
  23. const [modelConfigs, setModelConfigs] = useState<ModelConfig[]>(DEFAULT_MODELS)
  24. // Disable drag drop when view changes
  25. useEffect(() => {
  26. // ビュー切り替え時にドラッグアンドドロップ機能を一時的に無効にして、ナビゲーション時の点滅を防ぐ
  27. setDragDropEnabled(false);
  28. setNotebookDragDropEnabled(false);
  29. // 次のイベントループで有効にして、ビュー切り替えが完了することを確認
  30. const timer = setTimeout(() => {
  31. setDragDropEnabled(true);
  32. setNotebookDragDropEnabled(true);
  33. }, 0);
  34. return () => {
  35. clearTimeout(timer);
  36. };
  37. }, [currentView]);
  38. // Load token from localStorage on initial render
  39. useEffect(() => {
  40. const verifyToken = async () => {
  41. const storedToken = localStorage.getItem('authToken')
  42. if (storedToken) {
  43. try {
  44. const userProfile = await authService.getProfile(storedToken) // Get full profile
  45. setAuthToken(storedToken)
  46. setCurrentUser(userProfile) // Store user profile
  47. } catch (error) {
  48. console.error('Invalid token, logging out:', error)
  49. localStorage.removeItem('authToken')
  50. setAuthToken(null)
  51. setCurrentUser(null)
  52. }
  53. }
  54. setIsVerifying(false)
  55. }
  56. verifyToken()
  57. }, [])
  58. const handleLoginSuccess = async (token: string) => {
  59. setAuthToken(token)
  60. localStorage.setItem('authToken', token)
  61. try {
  62. const userProfile = await authService.getProfile(token)
  63. setCurrentUser(userProfile)
  64. } catch (error) {
  65. console.error('Failed to fetch user profile:', error)
  66. }
  67. }
  68. const handleLogout = () => {
  69. setAuthToken(null)
  70. setCurrentUser(null)
  71. localStorage.removeItem('authToken')
  72. }
  73. // Fetch Models
  74. const fetchAndSetModels = React.useCallback(async () => {
  75. if (!authToken) return
  76. try {
  77. const backendModels = await modelConfigService.getAll(authToken)
  78. const mergedModelsMap = new Map<string, ModelConfig>()
  79. DEFAULT_MODELS.forEach(m => mergedModelsMap.set(m.id, m))
  80. backendModels.forEach(bm => {
  81. mergedModelsMap.set(bm.id, { ...bm })
  82. })
  83. const mergedModels = Array.from(mergedModelsMap.values())
  84. setModelConfigs(mergedModels)
  85. } catch (error) {
  86. console.error('Failed to fetch model configs:', error)
  87. setModelConfigs(DEFAULT_MODELS)
  88. }
  89. }, [authToken])
  90. useEffect(() => {
  91. if (authToken) {
  92. fetchAndSetModels()
  93. }
  94. }, [authToken, fetchAndSetModels])
  95. const handleUpdateModels = React.useCallback(async (action: 'create' | 'update' | 'delete', model: ModelConfig) => {
  96. if (!authToken) return
  97. try {
  98. if (action === 'create') {
  99. await modelConfigService.create(authToken, model)
  100. } else if (action === 'update') {
  101. await modelConfigService.update(authToken, model.id, model)
  102. } else if (action === 'delete') {
  103. await modelConfigService.remove(authToken, model.id)
  104. }
  105. await fetchAndSetModels()
  106. } catch (error) {
  107. console.error(`Failed to perform ${action} on model config:`, error)
  108. throw error
  109. }
  110. }, [authToken, fetchAndSetModels])
  111. const handleChatWithContext = (context: { selectedGroups?: string[], selectedFiles?: string[] }) => {
  112. setChatContext(context)
  113. setCurrentView('chat')
  114. }
  115. // Login Flow
  116. if (isVerifying) {
  117. return (
  118. <div className="h-screen w-full flex items-center justify-center bg-slate-50">
  119. <div className="flex flex-col items-center gap-4">
  120. <div className="w-12 h-12 border-4 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
  121. <p className="text-slate-600 font-medium">Verifying session...</p>
  122. </div>
  123. </div>
  124. )
  125. }
  126. if (!authToken) {
  127. return <LoginPage onLoginSuccess={handleLoginSuccess} />
  128. }
  129. // Main Layout (Rail + View)
  130. return (
  131. <div className='flex h-screen w-full bg-slate-50 overflow-hidden relative'>
  132. <SidebarRail
  133. currentView={currentView}
  134. onViewChange={setCurrentView}
  135. onLogout={handleLogout}
  136. currentUser={currentUser}
  137. />
  138. <div className="flex-1 overflow-hidden relative">
  139. {currentView === 'chat' && (
  140. <ChatView
  141. authToken={authToken}
  142. onLogout={handleLogout}
  143. modelConfigs={modelConfigs}
  144. onNavigate={(view) => setCurrentView(view)}
  145. initialChatContext={chatContext}
  146. onClearContext={() => setChatContext(null)}
  147. />
  148. )}
  149. {currentView === 'knowledge' && (
  150. <KnowledgeBaseView
  151. authToken={authToken}
  152. onLogout={handleLogout}
  153. modelConfigs={modelConfigs}
  154. onNavigate={(view) => setCurrentView(view)}
  155. isAdmin={!!currentUser?.isAdmin}
  156. />
  157. )}
  158. {currentView === 'notebooks' && (
  159. <NotebooksView
  160. authToken={authToken}
  161. onChatWithContext={handleChatWithContext}
  162. isAdmin={!!currentUser?.isAdmin}
  163. />
  164. )}
  165. {currentView === 'settings' && (
  166. <SettingsView
  167. models={modelConfigs}
  168. onUpdateModels={handleUpdateModels}
  169. authToken={authToken}
  170. isAdmin={!!currentUser?.isAdmin} // Pass isAdmin status
  171. currentUser={currentUser} // Pass current user
  172. />
  173. )}
  174. </div>
  175. </div>
  176. )
  177. }
  178. const App: React.FC = () => {
  179. return (
  180. <LanguageProvider>
  181. <ToastProvider>
  182. <AppContent />
  183. </ToastProvider>
  184. </LanguageProvider>
  185. )
  186. }
  187. export default App