feat: prepare Zhinian desktop pilot
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

This commit is contained in:
inman
2026-05-07 21:49:20 +08:00
parent cddaf37016
commit 0abc48189c
103 changed files with 10975 additions and 2049 deletions

View File

@@ -4,11 +4,13 @@ import {
FolderUp,
Search,
ShieldCheck,
Trash2,
UploadCloud,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { toast } from 'sonner';
import { invokeIpc, toUserMessage } from '@/lib/api-client';
import { hostApiFetch } from '@/lib/host-api';
@@ -82,6 +84,8 @@ export function Knowledge() {
const [files, setFiles] = useState<KnowledgeFile[]>([]);
const [query, setQuery] = useState('');
const [loading, setLoading] = useState(false);
const [fileToDelete, setFileToDelete] = useState<KnowledgeFile | null>(null);
const [deletingId, setDeletingId] = useState<string | null>(null);
const config = useYinianStore((state) => state.config);
const workspaceId = config?.hotel.id ?? 'default';
@@ -142,9 +146,27 @@ export function Knowledge() {
}
};
const handleDelete = async () => {
if (!fileToDelete) return;
setDeletingId(fileToDelete.id);
try {
await hostApiFetch<{ success: boolean }>(
`/api/knowledge/files/${encodeURIComponent(fileToDelete.id)}?workspaceId=${encodeURIComponent(workspaceId)}`,
{ method: 'DELETE' },
);
setFiles((current) => current.filter((file) => file.id !== fileToDelete.id));
toast.success(t('knowledge.toast.deleted'));
setFileToDelete(null);
} catch (error) {
toast.error(t('knowledge.toast.deleteFailed', { message: toUserMessage(error) }));
} finally {
setDeletingId(null);
}
};
return (
<YinianPageShell data-testid="knowledge-page">
<YinianPageHeader>
<YinianPageHeader className="yinian-visual-band rounded-lg border border-[#D5E8F3]/75 p-5">
<div>
<h1 className="text-3xl font-semibold tracking-normal text-slate-950 dark:text-slate-50">
{t('knowledge.title')}
@@ -205,10 +227,10 @@ export function Knowledge() {
</div>
</YinianEmptyState>
) : (
<div className="mt-4 divide-y divide-slate-200 dark:divide-white/10">
<div className="mt-4 grid gap-2">
{visibleFiles.map((file) => (
<div key={file.id} className="flex items-center gap-3 py-3">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-slate-100 text-[#1E3A8A] dark:bg-slate-900 dark:text-blue-100">
<div key={file.id} className="flex items-center gap-3 rounded-lg border border-slate-200/70 bg-white px-3 py-3 dark:border-white/10 dark:bg-white/5">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-[#D5E8F3]/80 bg-[#EAF5FA] text-[#075985] dark:border-white/10 dark:bg-white/10 dark:text-blue-100">
<FileText className="h-4 w-4" />
</div>
<div className="min-w-0 flex-1">
@@ -221,11 +243,32 @@ export function Knowledge() {
</div>
</div>
<Badge variant="success" className="shrink-0">{t('knowledge.backedUp')}</Badge>
<Button
type="button"
variant="ghost"
size="icon"
className="h-8 w-8 shrink-0 text-muted-foreground hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-950/30"
title={t('knowledge.delete')}
disabled={deletingId === file.id}
onClick={() => setFileToDelete(file)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
</div>
)}
</YinianPanel>
<ConfirmDialog
open={!!fileToDelete}
title={t('knowledge.delete')}
message={t('knowledge.deleteConfirm', { name: fileToDelete?.name })}
confirmLabel={t('knowledge.delete')}
cancelLabel={t('common:actions.cancel')}
variant="destructive"
onConfirm={() => void handleDelete()}
onCancel={() => setFileToDelete(null)}
/>
</YinianPageShell>
);
}