workspace working
This commit is contained in:
@@ -267,6 +267,7 @@ const axiosInstance = axios.create({
|
||||
axiosInstance.interceptors.request.use((config) => {
|
||||
const apiKey = useSettingsStore.getState().apiKey
|
||||
const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
|
||||
const workspace = useSettingsStore.getState().workspace
|
||||
|
||||
// Always include token if it exists, regardless of path
|
||||
if (token) {
|
||||
@@ -275,6 +276,10 @@ axiosInstance.interceptors.request.use((config) => {
|
||||
if (apiKey) {
|
||||
config.headers['X-API-Key'] = apiKey
|
||||
}
|
||||
// Add workspace header if workspace is selected
|
||||
if (workspace) {
|
||||
config.headers['X-Workspace'] = workspace
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
@@ -360,6 +365,7 @@ export const queryTextStream = async (
|
||||
) => {
|
||||
const apiKey = useSettingsStore.getState().apiKey;
|
||||
const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
|
||||
const workspace = useSettingsStore.getState().workspace;
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/x-ndjson',
|
||||
@@ -370,6 +376,9 @@ export const queryTextStream = async (
|
||||
if (apiKey) {
|
||||
headers['X-API-Key'] = apiKey;
|
||||
}
|
||||
if (workspace) {
|
||||
headers['X-Workspace'] = workspace;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${backendBaseUrl}/query/stream`, {
|
||||
@@ -779,3 +788,8 @@ export const createWorkspace = async (name: string): Promise<WorkspaceResponse>
|
||||
const response = await axiosInstance.post('/workspaces/', { name })
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const deleteWorkspace = async (name: string): Promise<{ message: string }> => {
|
||||
const response = await axiosInstance.delete(`/workspaces/${encodeURIComponent(name)}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useEffect, useState, ChangeEvent, KeyboardEvent } from 'react'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
import { listWorkspaces, createWorkspace } from '@/api/lightrag'
|
||||
import { listWorkspaces, createWorkspace, deleteWorkspace } 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 { PlusIcon, SearchIcon, TrashIcon } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export function WorkspaceSelector() {
|
||||
@@ -18,6 +18,8 @@ export function WorkspaceSelector() {
|
||||
const [search, setSearch] = useState('')
|
||||
const [newWorkspaceName, setNewWorkspaceName] = useState('')
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
|
||||
|
||||
// Fetch workspaces on mount
|
||||
useEffect(() => {
|
||||
@@ -56,12 +58,29 @@ export function WorkspaceSelector() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteWorkspace = async () => {
|
||||
if (!workspace) return
|
||||
setDeleting(true)
|
||||
try {
|
||||
await deleteWorkspace(workspace)
|
||||
await fetchWorkspaces()
|
||||
// Clear selected workspace if it was deleted
|
||||
setWorkspace('')
|
||||
setShowDeleteDialog(false)
|
||||
} catch (error) {
|
||||
console.error('Failed to delete workspace:', error)
|
||||
alert(`Failed to delete workspace: ${error}`)
|
||||
} finally {
|
||||
setDeleting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const filteredWorkspaces = workspaceList.filter(name =>
|
||||
name.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Select
|
||||
value={workspace || ''}
|
||||
onValueChange={(value) => setWorkspace(value)}
|
||||
@@ -95,42 +114,79 @@ export function WorkspaceSelector() {
|
||||
</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')}
|
||||
<div className="flex items-center gap-2">
|
||||
{workspace && (
|
||||
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="icon" variant="outline" className="text-destructive">
|
||||
<TrashIcon className="size-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('workspace.deleteTitle')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">
|
||||
{t('workspace.deleteConfirm', { workspace })}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('workspace.deleteWarning')}
|
||||
</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">{t('common.cancel')}</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
onClick={handleDeleteWorkspace}
|
||||
disabled={deleting}
|
||||
variant="destructive"
|
||||
>
|
||||
{deleting ? t('common.deleting') : t('common.delete')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="icon" variant="outline">
|
||||
<PlusIcon className="size-4" />
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -202,6 +202,7 @@ export default function DocumentManager() {
|
||||
const setShowFileName = useSettingsStore.use.setShowFileName()
|
||||
const documentsPageSize = useSettingsStore.use.documentsPageSize()
|
||||
const setDocumentsPageSize = useSettingsStore.use.setDocumentsPageSize()
|
||||
const workspace = useSettingsStore.use.workspace()
|
||||
|
||||
// New pagination state
|
||||
const [currentPageDocs, setCurrentPageDocs] = useState<DocStatusResponse[]>([])
|
||||
@@ -901,6 +902,7 @@ export default function DocumentManager() {
|
||||
statusFilter,
|
||||
sortField,
|
||||
sortDirection,
|
||||
workspace,
|
||||
fetchPaginatedDocuments
|
||||
]);
|
||||
|
||||
|
||||
@@ -68,70 +68,76 @@ export default function SiteHeader() {
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="border-border/40 bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 flex h-10 w-full border-b px-4 backdrop-blur">
|
||||
<div className="min-w-[200px] w-auto flex items-center">
|
||||
<a href={webuiPrefix} className="flex items-center gap-2">
|
||||
<ZapIcon className="size-4 text-emerald-400" aria-hidden="true" />
|
||||
<span className="font-bold md:inline-block">{SiteInfo.name}</span>
|
||||
</a>
|
||||
{webuiTitle && (
|
||||
<div className="flex items-center">
|
||||
<span className="mx-1 text-xs text-gray-500 dark:text-gray-400">|</span>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="font-medium text-sm cursor-default">
|
||||
{webuiTitle}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
{webuiDescription && (
|
||||
<TooltipContent side="bottom">
|
||||
{webuiDescription}
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</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">
|
||||
<TabsNavigation />
|
||||
{isGuestMode && (
|
||||
<div className="ml-2 self-center px-2 py-1 text-xs bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200 rounded-md">
|
||||
{t('login.guestMode', 'Guest Mode')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<nav className="w-[200px] flex items-center justify-end">
|
||||
<div className="flex items-center gap-2">
|
||||
{versionDisplay && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 mr-1">
|
||||
v{versionDisplay}
|
||||
</span>
|
||||
)}
|
||||
<Button variant="ghost" size="icon" side="bottom" tooltip={t('header.projectRepository')}>
|
||||
<a href={SiteInfo.github} target="_blank" rel="noopener noreferrer">
|
||||
<GithubIcon className="size-4" aria-hidden="true" />
|
||||
</a>
|
||||
</Button>
|
||||
<AppSettings />
|
||||
{!isGuestMode && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
side="bottom"
|
||||
tooltip={`${t('header.logout')} (${username})`}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOutIcon className="size-4" aria-hidden="true" />
|
||||
</Button>
|
||||
<header className="border-border/40 bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 flex flex-col w-full border-b px-4 backdrop-blur">
|
||||
{/* First row: Title and other header items */}
|
||||
<div className="flex h-10 items-center justify-between">
|
||||
<div className="min-w-[200px] w-auto flex items-center">
|
||||
<a href={webuiPrefix} className="flex items-center gap-2">
|
||||
<ZapIcon className="size-4 text-emerald-400" aria-hidden="true" />
|
||||
<span className="font-bold md:inline-block">{SiteInfo.name}</span>
|
||||
</a>
|
||||
{webuiTitle && (
|
||||
<div className="flex items-center">
|
||||
<span className="mx-1 text-xs text-gray-500 dark:text-gray-400">|</span>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="font-medium text-sm cursor-default">
|
||||
{webuiTitle}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
{webuiDescription && (
|
||||
<TooltipContent side="bottom">
|
||||
{webuiDescription}
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className="flex h-10 flex-1 items-center justify-center">
|
||||
<TabsNavigation />
|
||||
{isGuestMode && (
|
||||
<div className="ml-2 self-center px-2 py-1 text-xs bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200 rounded-md">
|
||||
{t('login.guestMode', 'Guest Mode')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<nav className="w-[200px] flex items-center justify-end">
|
||||
<div className="flex items-center gap-2">
|
||||
{versionDisplay && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 mr-1">
|
||||
v{versionDisplay}
|
||||
</span>
|
||||
)}
|
||||
<Button variant="ghost" size="icon" side="bottom" tooltip={t('header.projectRepository')}>
|
||||
<a href={SiteInfo.github} target="_blank" rel="noopener noreferrer">
|
||||
<GithubIcon className="size-4" aria-hidden="true" />
|
||||
</a>
|
||||
</Button>
|
||||
<AppSettings />
|
||||
{!isGuestMode && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
side="bottom"
|
||||
tooltip={`${t('header.logout')} (${username})`}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<LogOutIcon className="size-4" aria-hidden="true" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Second row: Workspace selector */}
|
||||
<div className="flex items-center gap-2 py-1 border-t border-border/40">
|
||||
<WorkspaceSelector />
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,10 @@
|
||||
"namePlaceholder": "Workspace name",
|
||||
"createDescription": "Create a new isolated workspace for your documents and indexes.",
|
||||
"create": "Create",
|
||||
"creating": "Creating..."
|
||||
"creating": "Creating...",
|
||||
"deleteTitle": "Delete Workspace",
|
||||
"deleteConfirm": "Are you sure you want to delete workspace '{{workspace}}'?",
|
||||
"deleteWarning": "This will permanently delete all data in this workspace and cannot be undone."
|
||||
},
|
||||
"login": {
|
||||
"description": "Please enter your account and password to log in to the system",
|
||||
@@ -47,7 +50,9 @@
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"saveFailed": "Save failed"
|
||||
"saveFailed": "Save failed",
|
||||
"delete": "Delete",
|
||||
"deleting": "Deleting..."
|
||||
},
|
||||
"documentPanel": {
|
||||
"clearDocuments": {
|
||||
|
||||
Reference in New Issue
Block a user