Auto-commit: OCR workflow improvements, performance optimizations, and bug fixes
This commit is contained in:
@@ -763,3 +763,19 @@ export const getDocumentStatusCounts = async (): Promise<StatusCountsResponse> =
|
||||
const response = await axiosInstance.get('/documents/status_counts')
|
||||
return response.data
|
||||
}
|
||||
|
||||
// Workspace API
|
||||
export type WorkspaceResponse = {
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export const listWorkspaces = async (): Promise<WorkspaceResponse[]> => {
|
||||
const response = await axiosInstance.get('/workspaces/')
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const createWorkspace = async (name: string): Promise<WorkspaceResponse> => {
|
||||
const response = await axiosInstance.post('/workspaces/', { name })
|
||||
return response.data
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import { useEffect, useState, ChangeEvent, KeyboardEvent } from 'react'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
import { listWorkspaces, createWorkspace } from '@/api/lightrag'
|
||||
import Button from '@/components/ui/Button'
|
||||
import Input from '@/components/ui/Input'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/Select'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, DialogClose } from '@/components/ui/Dialog'
|
||||
import { PlusIcon, SearchIcon } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export function WorkspaceSelector() {
|
||||
const { t } = useTranslation()
|
||||
const workspace = useSettingsStore.use.workspace()
|
||||
const workspaceList = useSettingsStore.use.workspaceList()
|
||||
const setWorkspace = useSettingsStore.use.setWorkspace()
|
||||
const setWorkspaceList = useSettingsStore.use.setWorkspaceList()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
const [newWorkspaceName, setNewWorkspaceName] = useState('')
|
||||
const [creating, setCreating] = useState(false)
|
||||
|
||||
// Fetch workspaces on mount
|
||||
useEffect(() => {
|
||||
fetchWorkspaces()
|
||||
}, [])
|
||||
|
||||
const fetchWorkspaces = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const workspaces = await listWorkspaces()
|
||||
setWorkspaceList(workspaces.map(w => w.name))
|
||||
// If no workspace selected, select the first one if exists
|
||||
if (!workspace && workspaces.length > 0) {
|
||||
setWorkspace(workspaces[0].name)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch workspaces:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateWorkspace = async () => {
|
||||
if (!newWorkspaceName.trim()) return
|
||||
setCreating(true)
|
||||
try {
|
||||
await createWorkspace(newWorkspaceName.trim())
|
||||
await fetchWorkspaces()
|
||||
setWorkspace(newWorkspaceName.trim())
|
||||
setNewWorkspaceName('')
|
||||
} catch (error) {
|
||||
console.error('Failed to create workspace:', error)
|
||||
alert(`Failed to create workspace: ${error}`)
|
||||
} finally {
|
||||
setCreating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const filteredWorkspaces = workspaceList.filter(name =>
|
||||
name.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
value={workspace || ''}
|
||||
onValueChange={(value) => setWorkspace(value)}
|
||||
>
|
||||
<SelectTrigger className="w-48">
|
||||
<SelectValue placeholder={loading ? t('workspace.loading') : t('workspace.select')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<div className="px-2 py-1 border-b">
|
||||
<div className="flex items-center gap-1">
|
||||
<SearchIcon className="size-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t('workspace.search')}
|
||||
value={search}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => setSearch(e.target.value)}
|
||||
className="h-8 border-0 focus-visible:ring-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{filteredWorkspaces.length === 0 ? (
|
||||
<div className="px-3 py-2 text-sm text-muted-foreground">
|
||||
{t('workspace.noWorkspaces')}
|
||||
</div>
|
||||
) : (
|
||||
filteredWorkspaces.map(name => (
|
||||
<SelectItem key={name} value={name}>
|
||||
{name}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="icon" variant="outline">
|
||||
<PlusIcon className="size-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('workspace.createTitle')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
placeholder={t('workspace.namePlaceholder')}
|
||||
value={newWorkspaceName}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => setNewWorkspaceName(e.target.value)}
|
||||
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') handleCreateWorkspace()
|
||||
}}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('workspace.createDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">{t('common.cancel')}</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
onClick={handleCreateWorkspace}
|
||||
disabled={!newWorkspaceName.trim() || creating}
|
||||
>
|
||||
{creating ? t('common.creating') : t('common.create')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { navigationService } from '@/services/navigation'
|
||||
import { ZapIcon, GithubIcon, LogOutIcon } from 'lucide-react'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/Tooltip'
|
||||
import { WorkspaceSelector } from '@/components/WorkspaceSelector'
|
||||
|
||||
interface NavigationTabProps {
|
||||
value: string
|
||||
@@ -92,6 +93,8 @@ export default function SiteHeader() {
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
<span className="mx-2 text-xs text-gray-300 dark:text-gray-600">|</span>
|
||||
<WorkspaceSelector />
|
||||
</div>
|
||||
|
||||
<div className="flex h-10 flex-1 items-center justify-center">
|
||||
|
||||
@@ -18,6 +18,17 @@
|
||||
"switchToDark": "Switch to dark theme"
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"loading": "Loading...",
|
||||
"select": "Select workspace",
|
||||
"search": "Search workspaces...",
|
||||
"noWorkspaces": "No workspaces",
|
||||
"createTitle": "Create Workspace",
|
||||
"namePlaceholder": "Workspace name",
|
||||
"createDescription": "Create a new isolated workspace for your documents and indexes.",
|
||||
"create": "Create",
|
||||
"creating": "Creating..."
|
||||
},
|
||||
"login": {
|
||||
"description": "Please enter your account and password to log in to the system",
|
||||
"username": "Username",
|
||||
|
||||
@@ -73,6 +73,12 @@ interface SettingsState {
|
||||
|
||||
currentTab: Tab
|
||||
setCurrentTab: (tab: Tab) => void
|
||||
|
||||
// Workspace settings
|
||||
workspace: string | null
|
||||
setWorkspace: (workspace: string | null) => void
|
||||
workspaceList: string[]
|
||||
setWorkspaceList: (list: string[]) => void
|
||||
}
|
||||
|
||||
const useSettingsStoreBase = create<SettingsState>()(
|
||||
@@ -127,6 +133,10 @@ const useSettingsStoreBase = create<SettingsState>()(
|
||||
enable_rerank: true
|
||||
},
|
||||
|
||||
// Workspace settings
|
||||
workspace: null,
|
||||
workspaceList: [],
|
||||
|
||||
setTheme: (theme: Theme) => set({ theme }),
|
||||
|
||||
setLanguage: (language: Language) => {
|
||||
@@ -196,12 +206,15 @@ const useSettingsStoreBase = create<SettingsState>()(
|
||||
|
||||
setShowFileName: (show: boolean) => set({ showFileName: show }),
|
||||
setShowLegend: (show: boolean) => set({ showLegend: show }),
|
||||
setDocumentsPageSize: (size: number) => set({ documentsPageSize: size })
|
||||
setDocumentsPageSize: (size: number) => set({ documentsPageSize: size }),
|
||||
|
||||
setWorkspace: (workspace: string | null) => set({ workspace }),
|
||||
setWorkspaceList: (list: string[]) => set({ workspaceList: list })
|
||||
}),
|
||||
{
|
||||
name: 'settings-storage',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
version: 17,
|
||||
version: 18,
|
||||
migrate: (state: any, version: number) => {
|
||||
if (version < 2) {
|
||||
state.showEdgeLabel = false
|
||||
@@ -294,6 +307,11 @@ const useSettingsStoreBase = create<SettingsState>()(
|
||||
state.querySettings.history_turns = 0
|
||||
}
|
||||
}
|
||||
if (version < 18) {
|
||||
// Add workspace fields
|
||||
state.workspace = null
|
||||
state.workspaceList = []
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user