refac
This commit is contained in:
1 parent
60c9db1cb8
commit
73bdf86766
3 files changed
+148
-103
No files matched your search
@@ -185,6 +185,7 @@ async def process_uploaded_file(
|
|||||||
request,
|
request,
|
||||||
ProcessFileForm(file_id=file_item.id, collection_name=knowledge_id),
|
ProcessFileForm(file_id=file_item.id, collection_name=knowledge_id),
|
||||||
user=user,
|
user=user,
|
||||||
|
db=db_session,
|
||||||
)
|
)
|
||||||
log.info(f'Linked file {file_item.id} to knowledge {knowledge_id}')
|
log.info(f'Linked file {file_item.id} to knowledge {knowledge_id}')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ export const uploadFile = async (
|
|||||||
token: string,
|
token: string,
|
||||||
file: File,
|
file: File,
|
||||||
metadata?: object | null,
|
metadata?: object | null,
|
||||||
process?: boolean | null
|
process?: boolean | null,
|
||||||
|
stream: boolean = true
|
||||||
) => {
|
) => {
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('file', file);
|
data.append('file', file);
|
||||||
@@ -42,7 +43,7 @@ export const uploadFile = async (
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res) {
|
if (res && stream) {
|
||||||
const status = await getFileProcessStatus(token, res.id);
|
const status = await getFileProcessStatus(token, res.id);
|
||||||
|
|
||||||
if (status && status.ok) {
|
if (status && status.ok) {
|
||||||
|
|||||||
@@ -78,7 +78,8 @@
|
|||||||
let showNewDirectoryModal = false;
|
let showNewDirectoryModal = false;
|
||||||
|
|
||||||
let showSyncConfirmModal = false;
|
let showSyncConfirmModal = false;
|
||||||
let pendingSyncFiles: Array<{path: string, filename: string, file: File}> | null = null;
|
let pendingSyncFiles: Array<{ path: string; filename: string; file: File }> | null = null;
|
||||||
|
let syncing: string | null = null;
|
||||||
let showAccessControlModal = false;
|
let showAccessControlModal = false;
|
||||||
|
|
||||||
let minSize = 0;
|
let minSize = 0;
|
||||||
@@ -542,13 +543,17 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Collect files from a directory without uploading — returns {path, filename, file}[]
|
// Collect files from a directory without uploading — returns {path, filename, file}[]
|
||||||
const collectDirectoryFiles = async (): Promise<Array<{path: string, filename: string, file: File}> | null> => {
|
const collectDirectoryFiles = async (): Promise<Array<{
|
||||||
|
path: string;
|
||||||
|
filename: string;
|
||||||
|
file: File;
|
||||||
|
}> | null> => {
|
||||||
const isFileSystemAccessSupported = 'showDirectoryPicker' in window;
|
const isFileSystemAccessSupported = 'showDirectoryPicker' in window;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isFileSystemAccessSupported) {
|
if (isFileSystemAccessSupported) {
|
||||||
const dirHandle = await window.showDirectoryPicker();
|
const dirHandle = await window.showDirectoryPicker();
|
||||||
const collected: Array<{path: string, filename: string, file: File}> = [];
|
const collected: Array<{ path: string; filename: string; file: File }> = [];
|
||||||
|
|
||||||
async function traverse(handle: FileSystemDirectoryHandle, dirPath = '') {
|
async function traverse(handle: FileSystemDirectoryHandle, dirPath = '') {
|
||||||
for await (const entry of handle.values()) {
|
for await (const entry of handle.values()) {
|
||||||
@@ -580,8 +585,9 @@
|
|||||||
|
|
||||||
input.onchange = () => {
|
input.onchange = () => {
|
||||||
try {
|
try {
|
||||||
const files = Array.from(input.files || [])
|
const files = Array.from(input.files || []).filter(
|
||||||
.filter((file) => !hasHiddenFolder(file.webkitRelativePath) && !file.name.startsWith('.'));
|
(file) => !hasHiddenFolder(file.webkitRelativePath) && !file.name.startsWith('.')
|
||||||
|
);
|
||||||
|
|
||||||
const collected = files.map((file) => {
|
const collected = files.map((file) => {
|
||||||
const parts = file.webkitRelativePath.split('/');
|
const parts = file.webkitRelativePath.split('/');
|
||||||
@@ -618,92 +624,112 @@
|
|||||||
const syncDirectoryHandler = async () => {
|
const syncDirectoryHandler = async () => {
|
||||||
if (!pendingSyncFiles?.length) return;
|
if (!pendingSyncFiles?.length) return;
|
||||||
|
|
||||||
// ── 2. Compute checksums ──
|
try {
|
||||||
toast.info($i18n.t('Computing checksums...'));
|
// ── 2. Compute checksums ──
|
||||||
const manifest = await Promise.all(
|
syncing = $i18n.t('Computing checksums ({{count}} files)', {
|
||||||
pendingSyncFiles.map(async (entry) => ({
|
count: pendingSyncFiles.length
|
||||||
...entry,
|
|
||||||
checksum: await computeFileHash(entry.file),
|
|
||||||
size: entry.file.size
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
pendingSyncFiles = null;
|
|
||||||
|
|
||||||
// ── 3. Diff against knowledge base ──
|
|
||||||
toast.info($i18n.t('Comparing with knowledge base...'));
|
|
||||||
const diff = await syncKnowledgeDiff(
|
|
||||||
localStorage.token,
|
|
||||||
id,
|
|
||||||
manifest.map(({ filename, path, checksum, size }) => ({ filename, path, checksum, size }))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!diff) {
|
|
||||||
toast.error($i18n.t('Failed to compare files.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 4. mkdir — create missing directories (parents first) ──
|
|
||||||
const createdDirectoryIds: Record<string, string> = {};
|
|
||||||
for (const dirPath of diff.mkdir) {
|
|
||||||
const segments = dirPath.split('/');
|
|
||||||
const name = segments.at(-1)!;
|
|
||||||
const parentPath = segments.slice(0, -1).join('/');
|
|
||||||
const parentId = parentPath ? createdDirectoryIds[parentPath] : null;
|
|
||||||
|
|
||||||
const directory = await createKnowledgeDirectory(
|
|
||||||
localStorage.token, knowledge.id, name, parentId
|
|
||||||
);
|
|
||||||
if (directory) {
|
|
||||||
createdDirectoryIds[dirPath] = directory.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 5. Upload added + modified files ──
|
|
||||||
const filesToUpload = manifest.filter((entry) =>
|
|
||||||
diff.added.some((a: any) => a.filename === entry.filename && a.path === entry.path) ||
|
|
||||||
diff.modified.some((m: any) => m.filename === entry.filename && m.path === entry.path)
|
|
||||||
);
|
|
||||||
|
|
||||||
let uploadedCount = 0;
|
|
||||||
for (const entry of filesToUpload) {
|
|
||||||
const fileObject = new File([entry.file], entry.filename, { type: entry.file.type });
|
|
||||||
await uploadFile(localStorage.token, fileObject, {
|
|
||||||
knowledge_id: knowledge.id,
|
|
||||||
file_hash: entry.checksum,
|
|
||||||
directory_id: entry.path ? createdDirectoryIds[entry.path] : null
|
|
||||||
});
|
});
|
||||||
uploadedCount++;
|
const manifest = await Promise.all(
|
||||||
toast.info(
|
pendingSyncFiles.map(async (entry) => ({
|
||||||
$i18n.t('Uploading: {{current}}/{{total}}', {
|
...entry,
|
||||||
current: uploadedCount,
|
checksum: await computeFileHash(entry.file),
|
||||||
total: filesToUpload.length
|
size: entry.file.size
|
||||||
})
|
}))
|
||||||
);
|
);
|
||||||
}
|
pendingSyncFiles = null;
|
||||||
|
|
||||||
// ── 6. Cleanup — remove deleted files + rmdir orphaned directories ──
|
// ── 3. Diff against knowledge base ──
|
||||||
const staleFileIds = [
|
syncing = $i18n.t('Comparing with knowledge base...');
|
||||||
...diff.deleted.map((d: any) => d.file_id),
|
const diff = await syncKnowledgeDiff(
|
||||||
...diff.modified.map((m: any) => m.stale_file_id)
|
localStorage.token,
|
||||||
];
|
id,
|
||||||
|
manifest.map(({ filename, path, checksum, size }) => ({ filename, path, checksum, size }))
|
||||||
|
);
|
||||||
|
|
||||||
if (staleFileIds.length > 0 || diff.rmdir.length > 0) {
|
if (!diff) {
|
||||||
await syncKnowledgeCleanup(localStorage.token, id, staleFileIds, diff.rmdir);
|
toast.error($i18n.t('Failed to compare files.'));
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ── 7. Report ──
|
// ── 4. mkdir — create missing directories (parents first) ──
|
||||||
toast.success(
|
const createdDirectoryIds: Record<string, string> = {};
|
||||||
$i18n.t(
|
for (const dirPath of diff.mkdir) {
|
||||||
'Sync complete: {{added}} added, {{modified}} modified, {{deleted}} deleted, {{unmodified}} unmodified',
|
const segments = dirPath.split('/');
|
||||||
{
|
const name = segments.at(-1)!;
|
||||||
added: diff.added.length,
|
const parentPath = segments.slice(0, -1).join('/');
|
||||||
modified: diff.modified.length,
|
const parentId = parentPath ? createdDirectoryIds[parentPath] : null;
|
||||||
deleted: diff.deleted.length,
|
|
||||||
unmodified: diff.unmodified_count
|
const directory = await createKnowledgeDirectory(
|
||||||
|
localStorage.token,
|
||||||
|
knowledge.id,
|
||||||
|
name,
|
||||||
|
parentId
|
||||||
|
);
|
||||||
|
if (directory) {
|
||||||
|
createdDirectoryIds[dirPath] = directory.id;
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
);
|
|
||||||
init();
|
// ── 5. Upload added + modified files ──
|
||||||
|
const filesToUpload = manifest.filter(
|
||||||
|
(entry) =>
|
||||||
|
diff.added.some((a: any) => a.filename === entry.filename && a.path === entry.path) ||
|
||||||
|
diff.modified.some((m: any) => m.filename === entry.filename && m.path === entry.path)
|
||||||
|
);
|
||||||
|
|
||||||
|
let uploadedCount = 0;
|
||||||
|
for (const entry of filesToUpload) {
|
||||||
|
uploadedCount++;
|
||||||
|
const displayPath = entry.path ? `${entry.path}/${entry.filename}` : entry.filename;
|
||||||
|
syncing = $i18n.t('Uploading {{current}}/{{total}}: {{file}}', {
|
||||||
|
current: uploadedCount,
|
||||||
|
total: filesToUpload.length,
|
||||||
|
file: displayPath
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileObject = new File([entry.file], entry.filename, { type: entry.file.type });
|
||||||
|
await uploadFile(
|
||||||
|
localStorage.token,
|
||||||
|
fileObject,
|
||||||
|
{
|
||||||
|
knowledge_id: knowledge.id,
|
||||||
|
file_hash: entry.checksum,
|
||||||
|
directory_id: entry.path ? createdDirectoryIds[entry.path] : null
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
).catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 6. Cleanup — remove deleted files + rmdir orphaned directories ──
|
||||||
|
const staleFileIds = [
|
||||||
|
...diff.deleted.map((d: any) => d.file_id),
|
||||||
|
...diff.modified.map((m: any) => m.stale_file_id)
|
||||||
|
];
|
||||||
|
|
||||||
|
if (staleFileIds.length > 0 || diff.rmdir.length > 0) {
|
||||||
|
syncing = $i18n.t('Removing {{count}} stale files...', { count: staleFileIds.length });
|
||||||
|
await syncKnowledgeCleanup(localStorage.token, id, staleFileIds, diff.rmdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 7. Report ──
|
||||||
|
toast.success(
|
||||||
|
$i18n.t(
|
||||||
|
'Sync complete: {{added}} added, {{modified}} modified, {{deleted}} deleted, {{unmodified}} unmodified',
|
||||||
|
{
|
||||||
|
added: diff.added.length,
|
||||||
|
modified: diff.modified.length,
|
||||||
|
deleted: diff.deleted.length,
|
||||||
|
unmodified: diff.unmodified_count
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
init();
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(`${e}`);
|
||||||
|
} finally {
|
||||||
|
syncing = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addFileHandler = async (fileId) => {
|
const addFileHandler = async (fileId) => {
|
||||||
@@ -1216,17 +1242,17 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="hidden md:block">
|
<div class="hidden md:block">
|
||||||
<Tooltip content={$i18n.t('Click to copy ID')}>
|
<Tooltip content={$i18n.t('Click to copy ID')}>
|
||||||
<button
|
<button
|
||||||
class="text-xs text-gray-500 font-mono shrink-0 px-2 py-1 rounded-lg cursor-pointer hover:underline transition whitespace-nowrap"
|
class="text-xs text-gray-500 font-mono shrink-0 px-2 py-1 rounded-lg cursor-pointer hover:underline transition whitespace-nowrap"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
copyToClipboard(id);
|
copyToClipboard(id);
|
||||||
toast.success($i18n.t('ID copied to clipboard'));
|
toast.success($i18n.t('ID copied to clipboard'));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{id}
|
{id}
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1281,7 +1307,13 @@
|
|||||||
|
|
||||||
{#if currentDirectoryId !== null}
|
{#if currentDirectoryId !== null}
|
||||||
<div class="px-5 -mt-1 pb-2">
|
<div class="px-5 -mt-1 pb-2">
|
||||||
<KnowledgeBreadcrumbs rootLabel={knowledge.name} {breadcrumbs} onNavigate={(dirId) => navigateToDirectory(dirId)} onMoveFile={(fileId, dirId) => moveFileToDirectoryHandler(fileId, dirId)} onMoveDir={(dirId, targetId) => moveDirectoryHandler(dirId, targetId)} />
|
<KnowledgeBreadcrumbs
|
||||||
|
rootLabel={knowledge.name}
|
||||||
|
{breadcrumbs}
|
||||||
|
onNavigate={(dirId) => navigateToDirectory(dirId)}
|
||||||
|
onMoveFile={(fileId, dirId) => moveFileToDirectoryHandler(fileId, dirId)}
|
||||||
|
onMoveDir={(dirId, targetId) => moveDirectoryHandler(dirId, targetId)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -1341,6 +1373,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if syncing}
|
||||||
|
<div class="mx-2.5 mt-2">
|
||||||
|
<div class="flex items-center gap-2.5 rounded-xl py-2 px-3 bg-gray-50 dark:bg-gray-850">
|
||||||
|
<Spinner className="size-3.5 shrink-0" />
|
||||||
|
<div class="text-xs text-gray-500 dark:text-gray-400 truncate">
|
||||||
|
{syncing}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if fileItems !== null && fileItemsTotal !== null}
|
{#if fileItems !== null && fileItemsTotal !== null}
|
||||||
<div class="flex flex-row flex-1 gap-3 px-2.5 mt-2">
|
<div class="flex flex-row flex-1 gap-3 px-2.5 mt-2">
|
||||||
<div class="flex-1 flex">
|
<div class="flex-1 flex">
|
||||||
@@ -1372,14 +1415,14 @@
|
|||||||
deleteFileHandler(fileId);
|
deleteFileHandler(fileId);
|
||||||
}}
|
}}
|
||||||
onRename={(fileId, name) => renameFileHandler(fileId, name)}
|
onRename={(fileId, name) => renameFileHandler(fileId, name)}
|
||||||
onNavigateDirectory={(dirId) => navigateToDirectory(dirId)}
|
onNavigateDirectory={(dirId) => navigateToDirectory(dirId)}
|
||||||
onRenameDirectory={(id, name) => renameDirectoryHandler(id, name)}
|
onRenameDirectory={(id, name) => renameDirectoryHandler(id, name)}
|
||||||
onDeleteDirectory={(id) => confirmDeleteDirectory(id)}
|
onDeleteDirectory={(id) => confirmDeleteDirectory(id)}
|
||||||
onMoveFileToDirectory={(fileId, dirId) =>
|
onMoveFileToDirectory={(fileId, dirId) =>
|
||||||
moveFileToDirectoryHandler(fileId, dirId)}
|
moveFileToDirectoryHandler(fileId, dirId)}
|
||||||
onMoveDirectoryToDirectory={(dirId, targetId) => moveDirectoryHandler(dirId, targetId)}
|
onMoveDirectoryToDirectory={(dirId, targetId) =>
|
||||||
/>
|
moveDirectoryHandler(dirId, targetId)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if fileItemsTotal > 30}
|
{#if fileItemsTotal > 30}
|
||||||
|
|||||||
Reference in new issue
Block a user