App.tsx 8.0 KB

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