2025-11-20 11:00:42 +02:00
|
|
|
import {
|
|
|
|
|
type CreateDocumentRequest,
|
|
|
|
|
createDocumentRequest,
|
|
|
|
|
createDocumentResponse,
|
|
|
|
|
type DeleteDocumentRequest,
|
|
|
|
|
deleteDocumentRequest,
|
|
|
|
|
deleteDocumentResponse,
|
|
|
|
|
type GetDocumentByChunkRequest,
|
|
|
|
|
type GetDocumentRequest,
|
|
|
|
|
type GetDocumentsRequest,
|
2026-02-09 16:46:54 -08:00
|
|
|
type GetDocumentsStatusRequest,
|
2025-11-20 11:00:42 +02:00
|
|
|
type GetDocumentTypeCountsRequest,
|
2026-01-13 01:15:33 +02:00
|
|
|
type GetSurfsenseDocsRequest,
|
2025-11-20 11:00:42 +02:00
|
|
|
getDocumentByChunkRequest,
|
|
|
|
|
getDocumentByChunkResponse,
|
|
|
|
|
getDocumentRequest,
|
|
|
|
|
getDocumentResponse,
|
|
|
|
|
getDocumentsRequest,
|
2026-02-09 16:49:11 -08:00
|
|
|
getDocumentsResponse,
|
2026-02-09 16:46:54 -08:00
|
|
|
getDocumentsStatusRequest,
|
|
|
|
|
getDocumentsStatusResponse,
|
2025-11-20 11:00:42 +02:00
|
|
|
getDocumentTypeCountsRequest,
|
|
|
|
|
getDocumentTypeCountsResponse,
|
2026-01-12 18:06:51 +02:00
|
|
|
getSurfsenseDocsByChunkResponse,
|
2026-01-13 01:45:58 -08:00
|
|
|
getSurfsenseDocsRequest,
|
2026-01-13 01:15:33 +02:00
|
|
|
getSurfsenseDocsResponse,
|
2025-11-20 11:00:42 +02:00
|
|
|
type SearchDocumentsRequest,
|
2026-01-17 20:45:10 +05:30
|
|
|
type SearchDocumentTitlesRequest,
|
2025-11-20 11:00:42 +02:00
|
|
|
searchDocumentsRequest,
|
|
|
|
|
searchDocumentsResponse,
|
2026-01-17 20:45:10 +05:30
|
|
|
searchDocumentTitlesRequest,
|
|
|
|
|
searchDocumentTitlesResponse,
|
2025-11-20 11:00:42 +02:00
|
|
|
type UpdateDocumentRequest,
|
|
|
|
|
type UploadDocumentRequest,
|
|
|
|
|
updateDocumentRequest,
|
|
|
|
|
updateDocumentResponse,
|
|
|
|
|
uploadDocumentRequest,
|
|
|
|
|
uploadDocumentResponse,
|
|
|
|
|
} from "@/contracts/types/document.types";
|
|
|
|
|
import { ValidationError } from "../error";
|
|
|
|
|
import { baseApiService } from "./base-api.service";
|
|
|
|
|
|
|
|
|
|
class DocumentsApiService {
|
|
|
|
|
/**
|
|
|
|
|
* Get a list of documents with optional filtering and pagination
|
|
|
|
|
*/
|
|
|
|
|
getDocuments = async (request: GetDocumentsRequest) => {
|
|
|
|
|
const parsedRequest = getDocumentsRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
2025-12-20 05:10:47 -08:00
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
2025-11-20 11:00:42 +02:00
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transform query params to be string values
|
|
|
|
|
const transformedQueryParams = parsedRequest.data.queryParams
|
|
|
|
|
? Object.fromEntries(
|
|
|
|
|
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => {
|
|
|
|
|
// Handle array values (document_type)
|
|
|
|
|
if (Array.isArray(v)) {
|
|
|
|
|
return [k, v.join(",")];
|
|
|
|
|
}
|
|
|
|
|
return [k, String(v)];
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const queryParams = transformedQueryParams
|
|
|
|
|
? new URLSearchParams(transformedQueryParams).toString()
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
|
|
return baseApiService.get(`/api/v1/documents?${queryParams}`, getDocumentsResponse);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a single document by ID
|
|
|
|
|
*/
|
|
|
|
|
getDocument = async (request: GetDocumentRequest) => {
|
|
|
|
|
const parsedRequest = getDocumentRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
2025-12-20 05:10:47 -08:00
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
2025-11-20 11:00:42 +02:00
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return baseApiService.get(`/api/v1/documents/${request.id}`, getDocumentResponse);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create documents (extension, crawled URL, or YouTube video)
|
|
|
|
|
*/
|
|
|
|
|
createDocument = async (request: CreateDocumentRequest) => {
|
|
|
|
|
const parsedRequest = createDocumentRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
2025-12-20 05:10:47 -08:00
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
2025-11-20 11:00:42 +02:00
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return baseApiService.post(`/api/v1/documents`, createDocumentResponse, {
|
|
|
|
|
body: parsedRequest.data,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Upload document files
|
|
|
|
|
*/
|
|
|
|
|
uploadDocument = async (request: UploadDocumentRequest) => {
|
|
|
|
|
const parsedRequest = uploadDocumentRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
2025-12-20 05:10:47 -08:00
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
2025-11-20 11:00:42 +02:00
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create FormData for file upload
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
parsedRequest.data.files.forEach((file) => {
|
|
|
|
|
formData.append("files", file);
|
|
|
|
|
});
|
|
|
|
|
formData.append("search_space_id", String(parsedRequest.data.search_space_id));
|
2026-02-26 18:24:57 -08:00
|
|
|
formData.append("should_summarize", String(parsedRequest.data.should_summarize));
|
2025-11-20 11:00:42 +02:00
|
|
|
|
|
|
|
|
return baseApiService.postFormData(`/api/v1/documents/fileupload`, uploadDocumentResponse, {
|
|
|
|
|
body: formData,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-09 16:46:54 -08:00
|
|
|
/**
|
|
|
|
|
* Batch document status for async processing tracking
|
|
|
|
|
*/
|
|
|
|
|
getDocumentsStatus = async (request: GetDocumentsStatusRequest) => {
|
|
|
|
|
const parsedRequest = getDocumentsStatusRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
|
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { search_space_id, document_ids } = parsedRequest.data.queryParams;
|
|
|
|
|
const params = new URLSearchParams({
|
|
|
|
|
search_space_id: String(search_space_id),
|
|
|
|
|
document_ids: document_ids.join(","),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-09 16:49:11 -08:00
|
|
|
return baseApiService.get(
|
|
|
|
|
`/api/v1/documents/status?${params.toString()}`,
|
|
|
|
|
getDocumentsStatusResponse
|
|
|
|
|
);
|
2026-02-09 16:46:54 -08:00
|
|
|
};
|
|
|
|
|
|
2025-11-20 11:00:42 +02:00
|
|
|
/**
|
|
|
|
|
* Search documents by title
|
|
|
|
|
*/
|
|
|
|
|
searchDocuments = async (request: SearchDocumentsRequest) => {
|
|
|
|
|
const parsedRequest = searchDocumentsRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
2025-12-20 05:10:47 -08:00
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
2025-11-20 11:00:42 +02:00
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transform query params to be string values
|
|
|
|
|
const transformedQueryParams = parsedRequest.data.queryParams
|
|
|
|
|
? Object.fromEntries(
|
|
|
|
|
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => {
|
|
|
|
|
// Handle array values (document_type)
|
|
|
|
|
if (Array.isArray(v)) {
|
|
|
|
|
return [k, v.join(",")];
|
|
|
|
|
}
|
|
|
|
|
return [k, String(v)];
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const queryParams = transformedQueryParams
|
|
|
|
|
? new URLSearchParams(transformedQueryParams).toString()
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
|
|
return baseApiService.get(`/api/v1/documents/search?${queryParams}`, searchDocumentsResponse);
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-17 20:45:10 +05:30
|
|
|
/**
|
|
|
|
|
* Search document titles (lightweight, optimized for mention picker)
|
|
|
|
|
* Returns only id, title, document_type - no content or metadata
|
2026-01-17 21:44:10 +05:30
|
|
|
* @param request - The search request with query params
|
|
|
|
|
* @param signal - Optional AbortSignal for request cancellation
|
2026-01-17 20:45:10 +05:30
|
|
|
*/
|
2026-01-17 21:44:10 +05:30
|
|
|
searchDocumentTitles = async (request: SearchDocumentTitlesRequest, signal?: AbortSignal) => {
|
2026-01-17 20:45:10 +05:30
|
|
|
const parsedRequest = searchDocumentTitlesRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
|
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
|
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transform query params to be string values
|
|
|
|
|
const transformedQueryParams = Object.fromEntries(
|
|
|
|
|
Object.entries(parsedRequest.data.queryParams)
|
|
|
|
|
.filter(([, v]) => v !== undefined)
|
|
|
|
|
.map(([k, v]) => [k, String(v)])
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const queryParams = new URLSearchParams(transformedQueryParams).toString();
|
|
|
|
|
|
|
|
|
|
return baseApiService.get(
|
|
|
|
|
`/api/v1/documents/search/titles?${queryParams}`,
|
2026-01-17 21:44:10 +05:30
|
|
|
searchDocumentTitlesResponse,
|
|
|
|
|
{ signal }
|
2026-01-17 20:45:10 +05:30
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-20 11:00:42 +02:00
|
|
|
/**
|
|
|
|
|
* Get document type counts
|
|
|
|
|
*/
|
|
|
|
|
getDocumentTypeCounts = async (request: GetDocumentTypeCountsRequest) => {
|
|
|
|
|
// Validate the request
|
|
|
|
|
const parsedRequest = getDocumentTypeCountsRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
|
|
|
|
// Format a user friendly error message
|
2025-12-20 05:10:47 -08:00
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
2025-11-20 11:00:42 +02:00
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transform query params to be string values
|
|
|
|
|
const transformedQueryParams = parsedRequest.data.queryParams
|
|
|
|
|
? Object.fromEntries(
|
|
|
|
|
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => [k, String(v)])
|
|
|
|
|
)
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const queryParams = transformedQueryParams
|
|
|
|
|
? new URLSearchParams(transformedQueryParams).toString()
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
|
|
return baseApiService.get(
|
|
|
|
|
`/api/v1/documents/type-counts?${queryParams}`,
|
|
|
|
|
getDocumentTypeCountsResponse
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get document by chunk ID (includes all chunks)
|
|
|
|
|
*/
|
|
|
|
|
getDocumentByChunk = async (request: GetDocumentByChunkRequest) => {
|
|
|
|
|
// Validate the request
|
|
|
|
|
const parsedRequest = getDocumentByChunkRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
|
|
|
|
// Format a user friendly error message
|
2025-12-20 05:10:47 -08:00
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
2025-11-20 11:00:42 +02:00
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return baseApiService.get(
|
|
|
|
|
`/api/v1/documents/by-chunk/${request.chunk_id}`,
|
|
|
|
|
getDocumentByChunkResponse
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-09 18:21:59 +02:00
|
|
|
/**
|
|
|
|
|
* Get Surfsense documentation by chunk ID
|
|
|
|
|
* Used for resolving [citation:doc-XXX] citations
|
|
|
|
|
*/
|
|
|
|
|
getSurfsenseDocByChunk = async (chunkId: number) => {
|
|
|
|
|
return baseApiService.get(
|
|
|
|
|
`/api/v1/surfsense-docs/by-chunk/${chunkId}`,
|
2026-01-12 18:06:51 +02:00
|
|
|
getSurfsenseDocsByChunkResponse
|
2026-01-09 18:21:59 +02:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-13 01:15:33 +02:00
|
|
|
/**
|
|
|
|
|
* List all Surfsense documentation documents
|
2026-01-17 21:44:10 +05:30
|
|
|
* @param request - The request with query params
|
|
|
|
|
* @param signal - Optional AbortSignal for request cancellation
|
2026-01-13 01:15:33 +02:00
|
|
|
*/
|
2026-01-17 21:44:10 +05:30
|
|
|
getSurfsenseDocs = async (request: GetSurfsenseDocsRequest, signal?: AbortSignal) => {
|
2026-01-13 06:14:58 +02:00
|
|
|
const parsedRequest = getSurfsenseDocsRequest.safeParse(request);
|
2026-01-13 01:15:33 +02:00
|
|
|
|
2026-01-13 06:14:58 +02:00
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
|
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
|
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
2026-01-13 01:15:33 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-13 06:14:58 +02:00
|
|
|
// Transform query params to be string values
|
|
|
|
|
const transformedQueryParams = parsedRequest.data.queryParams
|
|
|
|
|
? Object.fromEntries(
|
|
|
|
|
Object.entries(parsedRequest.data.queryParams).map(([k, v]) => [k, String(v)])
|
|
|
|
|
)
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const queryParams = transformedQueryParams
|
|
|
|
|
? new URLSearchParams(transformedQueryParams).toString()
|
|
|
|
|
: "";
|
|
|
|
|
|
|
|
|
|
const url = `/api/v1/surfsense-docs?${queryParams}`;
|
2026-01-13 01:15:33 +02:00
|
|
|
|
2026-01-17 21:44:10 +05:30
|
|
|
return baseApiService.get(url, getSurfsenseDocsResponse, { signal });
|
2026-01-13 01:15:33 +02:00
|
|
|
};
|
|
|
|
|
|
2025-11-20 11:00:42 +02:00
|
|
|
/**
|
|
|
|
|
* Update a document
|
|
|
|
|
*/
|
|
|
|
|
updateDocument = async (request: UpdateDocumentRequest) => {
|
|
|
|
|
// Validate the request
|
|
|
|
|
const parsedRequest = updateDocumentRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
|
|
|
|
// Format a user friendly error message
|
2025-12-20 05:10:47 -08:00
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
2025-11-20 11:00:42 +02:00
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { id, data } = parsedRequest.data;
|
|
|
|
|
|
|
|
|
|
return baseApiService.put(`/api/v1/documents/${id}`, updateDocumentResponse, {
|
|
|
|
|
body: data,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Delete a document
|
|
|
|
|
*/
|
|
|
|
|
deleteDocument = async (request: DeleteDocumentRequest) => {
|
|
|
|
|
// Validate the request
|
|
|
|
|
const parsedRequest = deleteDocumentRequest.safeParse(request);
|
|
|
|
|
|
|
|
|
|
if (!parsedRequest.success) {
|
|
|
|
|
console.error("Invalid request:", parsedRequest.error);
|
|
|
|
|
|
|
|
|
|
// Format a user friendly error message
|
2025-12-20 05:10:47 -08:00
|
|
|
const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", ");
|
2025-11-20 11:00:42 +02:00
|
|
|
throw new ValidationError(`Invalid request: ${errorMessage}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return baseApiService.delete(`/api/v1/documents/${request.id}`, deleteDocumentResponse);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const documentsApiService = new DocumentsApiService();
|