Auto-commit: OCR workflow improvements, performance optimizations, and bug fixes

This commit is contained in:
2026-01-11 18:21:16 +08:00
parent 642dd0ea5f
commit 1ddd49f913
97 changed files with 5909 additions and 451 deletions

View File

@@ -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
}

View File

@@ -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>
)
}

View File

@@ -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">

View File

@@ -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",

View File

@@ -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
}
}