refactor: update knowledge document types and API client interfaces
- Refactored types in `Knowledge/types.ts` to introduce new interfaces for document handling. - Added `KnowledgeDocItem`, `KnowledgeDocsListResponse`, `KnowledgeDocsUploadInput`, `KnowledgeDocsUploadResponse`, and `KnowledgeDocsDeleteResponse` for better structure and clarity. - Updated `KnowledgeDocsApiClient` interface to include methods for listing, uploading, and deleting documents. fix: replace deprecated icons in AccountSettingsPanel and SettingMenu - Replaced `CheckCircleIcon` with `CheckCircle` from `lucide-react` in `AccountSettingsPanel.tsx`. - Updated `SettingMenu.tsx` to use `Settings` and `User` from `lucide-react` instead of custom icons. test: add tests for knowledge docs routes and KnowledgePage - Created `knowledge-docs-routes.test.ts` to test API routes for listing, uploading, and deleting knowledge documents. - Added `knowledge-page.test.tsx` to test the rendering and functionality of the KnowledgePage component, including document loading and deletion.
This commit is contained in:
132
src/lib/knowledge-docs-api.ts
Normal file
132
src/lib/knowledge-docs-api.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { hostApiFetch } from './host-api';
|
||||
import type {
|
||||
KnowledgeDocItem,
|
||||
KnowledgeDocsDeleteResponse,
|
||||
KnowledgeDocsListResponse,
|
||||
KnowledgeDocsUploadInput,
|
||||
KnowledgeDocsUploadResponse,
|
||||
} from '../pages/Knowledge/types';
|
||||
|
||||
type KnowledgeDocsListPayload = KnowledgeDocsListResponse | KnowledgeDocItem[] | { success?: boolean; files?: unknown; error?: string };
|
||||
type KnowledgeDocsUploadPayload = KnowledgeDocsUploadResponse | { success?: boolean; file?: unknown; files?: unknown; error?: string };
|
||||
type KnowledgeDocsDeletePayload = KnowledgeDocsDeleteResponse | { success?: boolean; error?: string };
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizeName(value: unknown): string {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
}
|
||||
|
||||
function normalizeSize(value: unknown): number {
|
||||
if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {
|
||||
return Math.floor(value);
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const parsed = Number(value);
|
||||
if (Number.isFinite(parsed) && parsed >= 0) {
|
||||
return Math.floor(parsed);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function normalizeModifiedAt(value: unknown): string {
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
return value.trim();
|
||||
}
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return new Date(value).toISOString();
|
||||
}
|
||||
return new Date(0).toISOString();
|
||||
}
|
||||
|
||||
function normalizeType(value: unknown, name: string): string {
|
||||
const rawType = typeof value === 'string' ? value.trim() : '';
|
||||
if (rawType) return rawType;
|
||||
|
||||
const dotIndex = name.lastIndexOf('.');
|
||||
if (dotIndex > 0 && dotIndex < name.length - 1) {
|
||||
return name.slice(dotIndex + 1).toLowerCase();
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function normalizeDocItem(value: unknown): KnowledgeDocItem | null {
|
||||
if (!isRecord(value)) return null;
|
||||
|
||||
const name = normalizeName(value.name ?? value.fileName ?? value.file ?? value.path);
|
||||
if (!name) return null;
|
||||
|
||||
return {
|
||||
name,
|
||||
size: normalizeSize(value.size ?? value.bytes ?? value.length),
|
||||
modifiedAt: normalizeModifiedAt(value.modifiedAt ?? value.updatedAt ?? value.mtime ?? value.lastModified),
|
||||
type: normalizeType(value.type ?? value.mimeType, name),
|
||||
};
|
||||
}
|
||||
|
||||
function extractListItems(payload: KnowledgeDocsListPayload): KnowledgeDocItem[] {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload.map(normalizeDocItem).filter((item): item is KnowledgeDocItem => Boolean(item));
|
||||
}
|
||||
|
||||
if (isRecord(payload)) {
|
||||
const files = Array.isArray(payload.files) ? payload.files : [];
|
||||
return files.map(normalizeDocItem).filter((item): item is KnowledgeDocItem => Boolean(item));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function extractSingleItem(payload: KnowledgeDocsUploadPayload): KnowledgeDocItem {
|
||||
if (isRecord(payload)) {
|
||||
const candidate = normalizeDocItem(payload.file ?? payload);
|
||||
if (candidate) return candidate;
|
||||
|
||||
const files = Array.isArray(payload.files) ? payload.files : [];
|
||||
for (const item of files) {
|
||||
const normalized = normalizeDocItem(item);
|
||||
if (normalized) return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Invalid knowledge docs upload response');
|
||||
}
|
||||
|
||||
function ensureSuccess(payload: { success?: boolean; error?: string } | null | undefined, fallbackError: string): void {
|
||||
if (payload && payload.success === false) {
|
||||
throw new Error(payload.error || fallbackError);
|
||||
}
|
||||
}
|
||||
|
||||
export const knowledgeDocsApi = {
|
||||
async list(): Promise<KnowledgeDocItem[]> {
|
||||
const response = await hostApiFetch<KnowledgeDocsListPayload>('/api/knowledge/docs');
|
||||
ensureSuccess(isRecord(response) ? response : undefined, 'Failed to load knowledge docs');
|
||||
return extractListItems(response).sort((left, right) => right.modifiedAt.localeCompare(left.modifiedAt));
|
||||
},
|
||||
|
||||
async upload(input: KnowledgeDocsUploadInput): Promise<KnowledgeDocItem> {
|
||||
const response = await hostApiFetch<KnowledgeDocsUploadPayload>('/api/knowledge/docs', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
ensureSuccess(isRecord(response) ? response : undefined, 'Failed to upload knowledge doc');
|
||||
return extractSingleItem(response);
|
||||
},
|
||||
|
||||
async delete(name: string): Promise<void> {
|
||||
const trimmedName = String(name ?? '').trim();
|
||||
if (!trimmedName) {
|
||||
throw new Error('Document name is required');
|
||||
}
|
||||
|
||||
const response = await hostApiFetch<KnowledgeDocsDeletePayload>(`/api/knowledge/docs/${encodeURIComponent(trimmedName)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
ensureSuccess(isRecord(response) ? response : undefined, 'Failed to delete knowledge doc');
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user