Selaa lähdekoodia

feat: add kb api

Thomas Zhang 3 kuukautta sitten
vanhempi
commit
6ce53236a0
4 muutettua tiedostoa jossa 384 lisäystä ja 0 poistoa
  1. 110 0
      src/lib/api/documents.ts
  2. 107 0
      src/lib/api/kbs.ts
  3. 86 0
      src/lib/stores/documents.svelte.ts
  4. 81 0
      src/lib/stores/kbs.svelte.ts

+ 110 - 0
src/lib/api/documents.ts

@@ -0,0 +1,110 @@
+import { resolve } from '$app/paths';
+
+import { handleResponse } from '$lib/api/types';
+import type { DocumentCR } from '$lib/types/custom-resources';
+import type { KBDocument } from '$lib/types/entities';
+import type { Changes, CoreResources } from '$lib/types/workspace';
+
+const list = (workspaceId: string, fetch = globalThis.fetch): Promise<KBDocument[]> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]', {
+			workspaceId,
+			resource: 'documents',
+		}),
+	).then(handleResponse<KBDocument[]>);
+
+const get = (workspaceId: string, id: string, fetch = globalThis.fetch): Promise<KBDocument> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]', {
+			workspaceId,
+			resource: 'documents',
+			id,
+		}),
+	).then(handleResponse<KBDocument>);
+
+const getChanges = (workspaceId: string, id: string, fetch = globalThis.fetch): Promise<Changes> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]/changes', {
+			workspaceId,
+			resource: 'documents',
+			id,
+		}),
+	).then(handleResponse<Changes>);
+
+const getStatus = (
+	workspaceId: string,
+	id: string,
+	fetch = globalThis.fetch,
+): Promise<DocumentCR['status']> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]/status', {
+			workspaceId,
+			resource: 'documents',
+			id,
+		}),
+	).then(handleResponse<DocumentCR['status']>);
+
+const getResources = (
+	workspaceId: string,
+	id: string,
+	fetch = globalThis.fetch,
+): Promise<CoreResources> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]/resources', {
+			workspaceId,
+			resource: 'documents',
+			id,
+		}),
+	).then(handleResponse<CoreResources>);
+
+const create = (
+	workspaceId: string,
+	kb: KBDocument,
+	fetch = globalThis.fetch,
+): Promise<KBDocument> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]', {
+			workspaceId,
+			resource: 'documents',
+		}),
+		{
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify(kb),
+		},
+	).then(handleResponse<KBDocument>);
+
+const update = (
+	workspaceId: string,
+	id: string,
+	kb: KBDocument,
+	fetch = globalThis.fetch,
+): Promise<KBDocument> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]', {
+			workspaceId,
+			resource: 'documents',
+			id,
+		}),
+		{
+			method: 'PUT',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify(kb),
+		},
+	).then(handleResponse<KBDocument>);
+
+const remove = (workspaceId: string, id: string, fetch = globalThis.fetch): Promise<void> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]', {
+			workspaceId,
+			resource: 'documents',
+			id,
+		}),
+		{
+			method: 'DELETE',
+		},
+	).then(async (res) => {
+		if (!res.ok) await handleResponse<void>(res);
+	});
+
+export { create, get, getChanges, getResources, getStatus, list, remove, update };

+ 107 - 0
src/lib/api/kbs.ts

@@ -0,0 +1,107 @@
+import { resolve } from '$app/paths';
+
+import { handleResponse } from '$lib/api/types';
+import type { ManagedKBCR } from '$lib/types/custom-resources';
+import type { KnowledgeBase } from '$lib/types/entities';
+import type { Changes, CoreResources } from '$lib/types/workspace';
+
+const list = (workspaceId: string, fetch = globalThis.fetch): Promise<KnowledgeBase[]> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]', {
+			workspaceId,
+			resource: 'kbs',
+		}),
+	).then(handleResponse<KnowledgeBase[]>);
+
+const get = (workspaceId: string, id: string, fetch = globalThis.fetch): Promise<KnowledgeBase> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]', {
+			workspaceId,
+			resource: 'kbs',
+			id,
+		}),
+	).then(handleResponse<KnowledgeBase>);
+
+const getChanges = (workspaceId: string, id: string, fetch = globalThis.fetch): Promise<Changes> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]/changes', {
+			workspaceId,
+			resource: 'kbs',
+			id,
+		}),
+	).then(handleResponse<Changes>);
+
+const getStatus = (
+	workspaceId: string,
+	id: string,
+	fetch = globalThis.fetch,
+): Promise<ManagedKBCR['status']> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]/status', {
+			workspaceId,
+			resource: 'kbs',
+			id,
+		}),
+	).then(handleResponse<ManagedKBCR['status']>);
+
+const getResources = (
+	workspaceId: string,
+	id: string,
+	fetch = globalThis.fetch,
+): Promise<CoreResources> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]/resources', {
+			workspaceId,
+			resource: 'kbs',
+			id,
+		}),
+	).then(handleResponse<CoreResources>);
+
+const create = (
+	workspaceId: string,
+	kb: KnowledgeBase,
+	fetch = globalThis.fetch,
+): Promise<KnowledgeBase> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]', { workspaceId, resource: 'kbs' }),
+		{
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify(kb),
+		},
+	).then(handleResponse<KnowledgeBase>);
+
+const update = (
+	workspaceId: string,
+	id: string,
+	kb: KnowledgeBase,
+	fetch = globalThis.fetch,
+): Promise<KnowledgeBase> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]', {
+			workspaceId,
+			resource: 'kbs',
+			id,
+		}),
+		{
+			method: 'PUT',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify(kb),
+		},
+	).then(handleResponse<KnowledgeBase>);
+
+const remove = (workspaceId: string, id: string, fetch = globalThis.fetch): Promise<void> =>
+	fetch(
+		resolve('/api/workspaces/[workspaceId]/[resource=resource]/[id]', {
+			workspaceId,
+			resource: 'kbs',
+			id,
+		}),
+		{
+			method: 'DELETE',
+		},
+	).then(async (res) => {
+		if (!res.ok) await handleResponse<void>(res);
+	});
+
+export { create, get, getChanges, getResources, getStatus, list, remove, update };

+ 86 - 0
src/lib/stores/documents.svelte.ts

@@ -0,0 +1,86 @@
+import * as documentsApi from '$lib/api/documents';
+import { notificationsStore } from '$lib/stores/notifications.svelte';
+import { ResourceStore } from '$lib/stores/resources.svelte';
+import type { DocumentCR } from '$lib/types/custom-resources';
+import type { KBDocument, KBDocumentStatus } from '$lib/types/entities';
+
+const statusFromCR = (status: DocumentCR['status']): KBDocumentStatus => {
+	if (!status || typeof status !== 'object') return 'pending';
+	if (status.phase === 'Failed') return 'error';
+	if (status.phase === 'Ready') return 'available';
+	return 'pending';
+};
+
+type CRStatus = DocumentCR['status'];
+
+class DocumentsStore extends ResourceStore<KBDocument, CRStatus> {
+	constructor() {
+		super('Knowledge Base', [
+			{
+				group: 'locostack.com',
+				version: 'v1alpha1',
+				resources: 'documents',
+			},
+		]);
+	}
+
+	protected resourceHref(id: string) {
+		return `/workloads/kbs/${id}`;
+	}
+
+	protected apiGet(workspaceId: string, id: string) {
+		return documentsApi.get(workspaceId, id);
+	}
+
+	protected apiGetStatus(workspaceId: string, id: string) {
+		return documentsApi.getStatus(workspaceId, id);
+	}
+
+	protected apiCreate(workspaceId: string, doc: KBDocument) {
+		return documentsApi.create(workspaceId, doc);
+	}
+
+	protected apiUpdate(workspaceId: string, doc: KBDocument) {
+		return documentsApi.update(workspaceId, doc.id, doc);
+	}
+
+	protected apiDelete(workspaceId: string, id: string) {
+		return documentsApi.remove(workspaceId, id);
+	}
+
+	protected onStatusChange(id: string, status: CRStatus, notify = true): void {
+		const item = this.get(id);
+		if (!item) return;
+		const deploymentStatus = statusFromCR(status);
+		notify = notify && item.status !== deploymentStatus;
+		item.status = deploymentStatus;
+		if (notify) {
+			this.#notifyStatusChange(id, item.name, deploymentStatus);
+		}
+	}
+
+	#notifyStatusChange(id: string, name: string, status: string): void {
+		const href = this.resourceHref(id);
+		notificationsStore.push({
+			type: 'success',
+			message: `Document "${name}" is now ${status}`,
+			href,
+		});
+	}
+
+	getDocuments(kbId: string): KBDocument[] {
+		return this.list.filter((doc) => doc.kb.id === kbId);
+	}
+
+	async createDocuments(docs: KBDocument[]): Promise<void> {
+		const result = await Promise.allSettled(
+			docs.map((doc) => documentsApi.create(this.workspaceId, doc)),
+		);
+		const added = result.filter((r) => r.status === 'fulfilled').map((r) => r.value);
+		this.list = [...this.list, ...added];
+	}
+}
+
+const documentsStore = new DocumentsStore();
+
+export { documentsStore };

+ 81 - 0
src/lib/stores/kbs.svelte.ts

@@ -0,0 +1,81 @@
+import * as kbsApi from '$lib/api/kbs';
+import { notificationsStore } from '$lib/stores/notifications.svelte';
+import { ResourceStore } from '$lib/stores/resources.svelte';
+import type { ManagedKBCR } from '$lib/types/custom-resources';
+import type { DeploymentStatus, KnowledgeBase } from '$lib/types/entities';
+
+type CRCondition = { type: string; status: 'True' | 'False' | 'Unknown' };
+
+const readyCondition = (conditions: CRCondition[]) =>
+	conditions.find((c) => c.type === 'Ready')?.status;
+
+const statusFromCR = (status: ManagedKBCR['status']): DeploymentStatus => {
+	if (!status || typeof status !== 'object') return 'not-deployed';
+	if (status.readyReplicas && status.readyReplicas > 0) return 'running';
+	const ready = readyCondition(status.conditions ?? []);
+	if (ready === 'True') return 'running';
+	if (ready === 'False') return 'degraded';
+	return 'pending';
+};
+
+type CRStatus = ManagedKBCR['status'];
+
+class KBsStore extends ResourceStore<KnowledgeBase, CRStatus> {
+	constructor() {
+		super('Knowledge Base', [
+			{
+				group: 'locostack.com',
+				version: 'v1alpha1',
+				resources: 'managedknowledgebases',
+			},
+		]);
+	}
+
+	protected resourceHref(id: string) {
+		return `/workloads/kbs/${id}`;
+	}
+
+	protected apiGet(workspaceId: string, id: string) {
+		return kbsApi.get(workspaceId, id);
+	}
+
+	protected apiGetStatus(workspaceId: string, id: string) {
+		return kbsApi.getStatus(workspaceId, id);
+	}
+
+	protected apiCreate(workspaceId: string, kb: KnowledgeBase) {
+		return kbsApi.create(workspaceId, kb);
+	}
+
+	protected apiUpdate(workspaceId: string, kb: KnowledgeBase) {
+		return kbsApi.update(workspaceId, kb.id, kb);
+	}
+
+	protected apiDelete(workspaceId: string, id: string) {
+		return kbsApi.remove(workspaceId, id);
+	}
+
+	protected onStatusChange(id: string, status: CRStatus, notify = true): void {
+		const item = this.get(id);
+		if (!item) return;
+		const deploymentStatus = statusFromCR(status);
+		notify = notify && item.status !== deploymentStatus;
+		item.status = deploymentStatus;
+		if (notify) {
+			this.#notifyStatusChange(id, item.name, deploymentStatus);
+		}
+	}
+
+	#notifyStatusChange(id: string, name: string, status: string): void {
+		const href = this.resourceHref(id);
+		notificationsStore.push({
+			type: 'success',
+			message: `Knowledge Base "${name}" is now ${status}`,
+			href,
+		});
+	}
+}
+
+const kbsStore = new KBsStore();
+
+export { kbsStore };