| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- import React from 'react'
- import { knowledgeGroupService } from '../../services/knowledgeGroupService'
- import { KnowledgeGroup, UpdateGroupData, CreateGroupData } from '../../types'
- import { Plus, Book, MoreVertical, Library, MessageSquare, Trash2, Edit2, FolderInput } from 'lucide-react'
- import { NotebookDetailView } from './NotebookDetailView'
- // import { InputDrawer } from '../InputDrawer' // Removed
- // import { EditNotebookDialog } from '../EditNotebookDialog' // Removed
- import { CreateNotebookDrawer } from '../CreateNotebookDrawer'
- import { EditNotebookDrawer } from '../EditNotebookDrawer'
- import { ImportFolderDrawer } from '../ImportFolderDrawer'
- import { useLanguage } from '../../contexts/LanguageContext'
- import { useToast } from '../../contexts/ToastContext'
- import { useConfirm } from '../../contexts/ConfirmContext'
- interface NotebooksViewProps {
- authToken: string
- onChatWithContext: (context: { selectedGroups?: string[], selectedFiles?: string[] }) => void
- isAdmin?: boolean
- }
- export const NotebooksView: React.FC<NotebooksViewProps> = ({ authToken, onChatWithContext, isAdmin = false }) => {
- const { t } = useLanguage()
- const { showError } = useToast()
- const { confirm } = useConfirm()
- const [notebooks, setNotebooks] = React.useState<KnowledgeGroup[]>([])
- const [isLoading, setIsLoading] = React.useState(true)
- const [selectedNotebook, setSelectedNotebook] = React.useState<KnowledgeGroup | null>(null)
- const [isCreateDrawerOpen, setIsCreateDrawerOpen] = React.useState(false)
- const [isImportDrawerOpen, setIsImportDrawerOpen] = React.useState(false) // Added
- const [editingNotebook, setEditingNotebook] = React.useState<KnowledgeGroup | null>(null)
- const fetchNotebooks = async () => {
- try {
- const groups = await knowledgeGroupService.getGroups()
- setNotebooks(groups)
- } catch (error) {
- console.error(error)
- } finally {
- setIsLoading(false)
- }
- }
- React.useEffect(() => {
- fetchNotebooks()
- }, [authToken, selectedNotebook]) // Refresh when going back
- const handleCreateNotebook = async (data: CreateGroupData) => {
- try {
- setIsLoading(true)
- await knowledgeGroupService.createGroup(data)
- const groups = await knowledgeGroupService.getGroups()
- setNotebooks(groups)
- setIsCreateDrawerOpen(false)
- } catch (error) {
- console.error(error)
- showError(t('createFailed'))
- } finally {
- setIsLoading(false)
- }
- }
- const handleUpdateNotebook = async (id: string, data: UpdateGroupData) => {
- await knowledgeGroupService.updateGroup(id, data)
- const groups = await knowledgeGroupService.getGroups()
- setNotebooks(groups)
- }
- const handleDeleteNotebook = async (e: React.MouseEvent, id: string, name: string) => {
- e.stopPropagation()
- if (!(await confirm(t('confirmDeleteNotebook').replace('$1', name)))) return
- try {
- setIsLoading(true)
- await knowledgeGroupService.deleteGroup(id)
- setNotebooks(prev => prev.filter(n => n.id !== id))
- } catch (error) {
- console.error(error)
- showError(t('deleteFailed'))
- } finally {
- setIsLoading(false)
- }
- }
- if (selectedNotebook) {
- return (
- <NotebookDetailView
- authToken={authToken}
- notebook={selectedNotebook}
- onBack={() => setSelectedNotebook(null)}
- onChatWithContext={onChatWithContext}
- isAdmin={!!isAdmin}
- />
- )
- }
- if (isLoading) {
- return <div className="p-8 text-center text-slate-500">{t('loading')}</div>
- }
- return (
- <div className="flex flex-col h-full bg-slate-50">
- <div className="p-6 border-b bg-white flex justify-between items-center">
- <div>
- <h1 className="text-xl font-bold text-slate-800 flex items-center gap-2">
- <Library className="w-6 h-6 text-blue-600" />
- <span className="bg-gradient-to-r from-blue-600 to-purple-600 text-transparent bg-clip-text">
- {t('notebooks')}
- </span>
- </h1>
- <p className="text-slate-500 mt-1">{t('notebooksDesc')}</p>
- </div>
- <div className="flex items-center gap-3">
- {isAdmin && (
- <button
- onClick={() => setIsImportDrawerOpen(true)}
- className="flex items-center gap-2 px-4 py-2 bg-white text-slate-700 text-sm font-medium rounded-xl border border-slate-200 hover:bg-slate-50 hover:border-slate-300 shadow-sm transition-all"
- >
- <FolderInput size={20} />
- <span>{t('importFolder')}</span>
- </button>
- )}
- {isAdmin && (
- <button
- onClick={() => setIsCreateDrawerOpen(true)}
- className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
- >
- <Plus size={20} />
- <span>{t('createNotebook')}</span>
- </button>
- )}
- </div>
- </div>
- <div className="flex-1 overflow-y-auto p-6">
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
- {notebooks.map((notebook) => (
- <div
- key={notebook.id}
- onClick={() => setSelectedNotebook(notebook)}
- className="bg-white rounded-xl shadow-sm border border-slate-200 hover:shadow-md transition-shadow p-5 cursor-pointer group"
- >
- <div className="flex justify-between items-start mb-3">
- <div className="p-3 bg-blue-50 text-blue-600 rounded-lg">
- <Book size={24} />
- </div>
- <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
- <button
- onClick={(e) => {
- e.stopPropagation()
- onChatWithContext({ selectedGroups: [notebook.id] })
- }}
- className="p-1 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
- title={t('chatWithNotebook')}
- >
- <MessageSquare size={20} />
- </button>
- {isAdmin && (
- <button
- onClick={(e) => {
- e.stopPropagation()
- setEditingNotebook(notebook)
- }}
- className="text-slate-400 hover:text-blue-500 p-1"
- title={t('editNotebook')}
- >
- <Edit2 size={20} />
- </button>
- )}
- {isAdmin && (
- <button
- onClick={(e) => handleDeleteNotebook(e, notebook.id, notebook.name)}
- className="text-slate-400 hover:text-red-500 p-1"
- title={t('deleteNotebook')}
- >
- <Trash2 size={20} />
- </button>
- )}
- </div>
- </div>
- <h3 className="text-lg font-semibold text-slate-900 mb-1">{notebook.name}</h3>
- <p className="text-slate-500 text-sm line-clamp-2 h-10">
- {notebook.description || t('noDescription')}
- </p>
- <div className="mt-4 pt-4 border-t border-slate-100 flex justify-between items-center text-xs text-slate-400">
- <span>{notebook.fileCount || 0} {t('files')}</span>
- <span>{notebook.updatedAt ? new Date(notebook.updatedAt).toLocaleDateString() : ''}</span>
- </div>
- </div>
- ))}
- {notebooks.length === 0 && (
- <div className="col-span-full text-center py-20 text-slate-400">
- {t('noNotebooks')}
- </div>
- )}
- </div>
- </div>
- {isCreateDrawerOpen && (
- <CreateNotebookDrawer
- isOpen={isCreateDrawerOpen}
- onClose={() => setIsCreateDrawerOpen(false)}
- onCreate={handleCreateNotebook}
- />
- )}
- {editingNotebook && (
- <EditNotebookDrawer
- isOpen={!!editingNotebook}
- onClose={() => setEditingNotebook(null)}
- notebook={editingNotebook}
- onUpdate={handleUpdateNotebook}
- />
- )}
- <ImportFolderDrawer
- isOpen={isImportDrawerOpen}
- onClose={() => setIsImportDrawerOpen(false)}
- authToken={authToken}
- onImportSuccess={fetchNotebooks}
- />
- </div>
- )
- }
|