97b7bb9890
Move batch reparse work to an asynq task to avoid HTTP timeouts, add i18n for all locales, guard in-flight documents, and prevent concurrent delete/reparse.
491 lines
16 KiB
TypeScript
491 lines
16 KiB
TypeScript
import { get, post, put, del, postUpload, getDown } from "../../utils/request";
|
|
import type { KnowledgeProcessOverrides } from '@/types/knowledgeProcess';
|
|
|
|
// 知识库管理 API(列表、创建、获取、更新、删除、复制)
|
|
export function listKnowledgeBases(params?: {
|
|
agent_id?: string;
|
|
/**
|
|
* Optional creator filter. Server-side semantics:
|
|
* - "mine" → only KBs whose creator_id matches the caller
|
|
* - "others" → only KBs created by someone else in this tenant
|
|
* - omitted/"all" → no filter
|
|
* KBs predating the RBAC backfill (creator_id="") never match
|
|
* mine/others — they fall out of both views by design.
|
|
*/
|
|
creator?: 'all' | 'mine' | 'others';
|
|
}) {
|
|
const query = new URLSearchParams();
|
|
if (params?.agent_id) query.set('agent_id', params.agent_id);
|
|
if (params?.creator && params.creator !== 'all') query.set('creator', params.creator);
|
|
const qs = query.toString();
|
|
return get(qs ? `/api/v1/knowledge-bases?${qs}` : '/api/v1/knowledge-bases');
|
|
}
|
|
|
|
// Read-only vector-store binding metadata enriched onto every KB
|
|
// response (list, create, get, update, pin). Source carries where the
|
|
// binding points; status reports whether that target is currently
|
|
// reachable by the server.
|
|
//
|
|
// - source 'env' → KB uses the tenant's env-configured store
|
|
// (RETRIEVE_DRIVER). vector_store_id is null and
|
|
// vector_store_name is the localized "System
|
|
// default" label; vector_store_engine_type still
|
|
// reports the underlying engine (e.g. "postgres").
|
|
// - source 'user' → KB is bound to a tenant-owned VectorStore.
|
|
// vector_store_id / name / engine_type are real.
|
|
// - source 'shared' → KB belongs to a different tenant and is
|
|
// readable via cross-organization sharing. The
|
|
// server strips vector_store_id and engine_type
|
|
// to avoid leaking the owner tenant's store
|
|
// inventory; only this source marker arrives.
|
|
// - status 'unavailable' → the binding cannot be reached right now
|
|
// (deleted row, registry miss, transient infra
|
|
// failure). Operators recover via the global
|
|
// Vector Stores settings page.
|
|
export type VectorStoreSource = 'env' | 'user' | 'shared' | 'unavailable';
|
|
export type VectorStoreStatus = 'available' | 'unavailable';
|
|
|
|
export interface KnowledgeBaseStoreView {
|
|
vector_store_id?: string | null;
|
|
vector_store_name?: string;
|
|
vector_store_engine_type?: string;
|
|
vector_store_source?: VectorStoreSource;
|
|
vector_store_status?: VectorStoreStatus;
|
|
}
|
|
|
|
export function createKnowledgeBase(data: {
|
|
name: string;
|
|
description?: string;
|
|
type?: 'document' | 'faq';
|
|
chunking_config?: any;
|
|
embedding_model_id?: string;
|
|
summary_model_id?: string;
|
|
// Opt-in binding to a specific tenant-owned VectorStore. Omit (or
|
|
// send undefined / empty string) to fall back to the env-configured
|
|
// store. Immutable after creation — UpdateKnowledgeBase intentionally
|
|
// does not accept this field.
|
|
vector_store_id?: string;
|
|
vlm_config?: {
|
|
enabled: boolean;
|
|
model_id?: string;
|
|
};
|
|
storage_provider_config?: { provider: string };
|
|
storage_config?: any; // legacy, kept for backward compat (dual-write)
|
|
asr_config?: {
|
|
enabled: boolean;
|
|
model_id?: string;
|
|
language?: string;
|
|
};
|
|
extract_config?: any;
|
|
faq_config?: { index_mode: string; question_index_mode?: string };
|
|
wiki_config?: {
|
|
synthesis_model_id?: string;
|
|
max_pages_per_ingest?: number;
|
|
extraction_granularity?: 'focused' | 'standard' | 'exhaustive';
|
|
};
|
|
indexing_strategy?: {
|
|
vector_enabled: boolean;
|
|
keyword_enabled: boolean;
|
|
wiki_enabled: boolean;
|
|
graph_enabled: boolean;
|
|
};
|
|
}) {
|
|
return post(`/api/v1/knowledge-bases`, data);
|
|
}
|
|
|
|
export function getKnowledgeBaseById(id: string, options?: { agent_id?: string }) {
|
|
const query = new URLSearchParams();
|
|
if (options?.agent_id) query.set('agent_id', options.agent_id);
|
|
const qs = query.toString();
|
|
return get(qs ? `/api/v1/knowledge-bases/${id}?${qs}` : `/api/v1/knowledge-bases/${id}`);
|
|
}
|
|
|
|
export function updateKnowledgeBase(id: string, data: {
|
|
name: string;
|
|
description?: string;
|
|
config?: {
|
|
chunking_config?: any;
|
|
image_processing_config?: any;
|
|
faq_config?: any;
|
|
wiki_config?: {
|
|
synthesis_model_id?: string;
|
|
max_pages_per_ingest?: number;
|
|
extraction_granularity?: 'focused' | 'standard' | 'exhaustive';
|
|
};
|
|
indexing_strategy?: {
|
|
vector_enabled: boolean;
|
|
keyword_enabled: boolean;
|
|
wiki_enabled: boolean;
|
|
graph_enabled: boolean;
|
|
};
|
|
}
|
|
}) {
|
|
return put(`/api/v1/knowledge-bases/${id}`, data);
|
|
}
|
|
|
|
export function rebuildKBIndex(kbId: string) {
|
|
return post(`/api/v1/knowledge-bases/${kbId}/rebuild-index`, {});
|
|
}
|
|
|
|
export function deleteKnowledgeBase(id: string) {
|
|
return del(`/api/v1/knowledge-bases/${id}`);
|
|
}
|
|
|
|
export function copyKnowledgeBase(data: { source_id: string; target_id?: string }) {
|
|
return post(`/api/v1/knowledge-bases/copy`, data);
|
|
}
|
|
|
|
// 获取可移动目标知识库列表(同类型、同Embedding模型)
|
|
export function listMoveTargets(sourceKbId: string) {
|
|
return get(`/api/v1/knowledge-bases/${sourceKbId}/move-targets`);
|
|
}
|
|
|
|
// 移动知识到其他知识库
|
|
export function moveKnowledge(data: {
|
|
knowledge_ids: string[];
|
|
source_kb_id: string;
|
|
target_kb_id: string;
|
|
mode: 'reuse_vectors' | 'reparse';
|
|
}) {
|
|
return post('/api/v1/knowledge/move', data);
|
|
}
|
|
|
|
// 获取知识移动进度
|
|
export function getKnowledgeMoveProgress(taskId: string) {
|
|
return get(`/api/v1/knowledge/move/progress/${taskId}`);
|
|
}
|
|
|
|
export function togglePinKnowledgeBase(id: string) {
|
|
return put(`/api/v1/knowledge-bases/${id}/pin`);
|
|
}
|
|
|
|
// 知识文件 API(基于具体知识库)
|
|
// data.tag_id: 可选,指定知识所属的分类ID
|
|
export function uploadKnowledgeFile(
|
|
kbId: string,
|
|
data: {
|
|
file: File
|
|
tag_id?: string
|
|
fileName?: string
|
|
process_config?: KnowledgeProcessOverrides | string
|
|
[key: string]: any
|
|
} = { file: new File([], '') },
|
|
onProgress?: (progressEvent: any) => void,
|
|
) {
|
|
const formData = new FormData();
|
|
Object.keys(data).forEach(key => {
|
|
const value = data[key];
|
|
if (value === undefined) return;
|
|
if (key === 'process_config' && value && typeof value !== 'string') {
|
|
formData.append(key, JSON.stringify(value));
|
|
} else {
|
|
formData.append(key, value);
|
|
}
|
|
});
|
|
return postUpload(`/api/v1/knowledge-bases/${kbId}/knowledge/file`, formData, onProgress);
|
|
}
|
|
|
|
// 从URL创建知识
|
|
// data.tag_id: 可选,指定知识所属的分类ID
|
|
export function createKnowledgeFromURL(
|
|
kbId: string,
|
|
data: { url: string; enable_multimodel?: boolean; tag_id?: string; process_config?: KnowledgeProcessOverrides },
|
|
) {
|
|
return post(`/api/v1/knowledge-bases/${kbId}/knowledge/url`, data);
|
|
}
|
|
|
|
// 手工创建知识
|
|
// data.tag_id: 可选,指定知识所属的分类ID
|
|
export function createManualKnowledge(
|
|
kbId: string,
|
|
data: {
|
|
title: string
|
|
content: string
|
|
status: string
|
|
tag_id?: string
|
|
process_config?: KnowledgeProcessOverrides
|
|
},
|
|
) {
|
|
return post(`/api/v1/knowledge-bases/${kbId}/knowledge/manual`, data);
|
|
}
|
|
|
|
export function listKnowledgeFiles(
|
|
kbId: string,
|
|
params: {
|
|
page: number;
|
|
page_size: number;
|
|
tag_id?: string;
|
|
keyword?: string;
|
|
file_type?: string;
|
|
parse_status?: string;
|
|
source?: string;
|
|
start_time?: string;
|
|
end_time?: string;
|
|
},
|
|
) {
|
|
const query = new URLSearchParams();
|
|
query.append('page', String(params.page));
|
|
query.append('page_size', String(params.page_size));
|
|
if (params.tag_id) query.append('tag_id', params.tag_id);
|
|
if (params.keyword) query.append('keyword', params.keyword);
|
|
if (params.file_type) query.append('file_type', params.file_type);
|
|
if (params.parse_status) query.append('parse_status', params.parse_status);
|
|
if (params.source) query.append('source', params.source);
|
|
if (params.start_time) query.append('start_time', params.start_time);
|
|
if (params.end_time) query.append('end_time', params.end_time);
|
|
const qs = query.toString();
|
|
return get(`/api/v1/knowledge-bases/${kbId}/knowledge?${qs}`);
|
|
}
|
|
|
|
export function getKnowledgeDetails(id: string, options?: { agent_id?: string }) {
|
|
const query = new URLSearchParams();
|
|
if (options?.agent_id) query.set('agent_id', options.agent_id);
|
|
const qs = query.toString();
|
|
return get(qs ? `/api/v1/knowledge/${id}?${qs}` : `/api/v1/knowledge/${id}`);
|
|
}
|
|
|
|
export function updateManualKnowledge(
|
|
id: string,
|
|
data: { title: string; content: string; status: string; process_config?: KnowledgeProcessOverrides },
|
|
) {
|
|
return put(`/api/v1/knowledge/manual/${id}`, data);
|
|
}
|
|
|
|
export function reparseKnowledge(id: string, data?: { process_config?: KnowledgeProcessOverrides }) {
|
|
return post(`/api/v1/knowledge/${id}/reparse`, data);
|
|
}
|
|
|
|
export function cancelKnowledgeParse(id: string) {
|
|
return post(`/api/v1/knowledge/${id}/cancel-parse`);
|
|
}
|
|
|
|
export function getKnowledgeSpans(id: string, attempt?: number) {
|
|
const qs = attempt ? `?attempt=${attempt}` : '';
|
|
return get(`/api/v1/knowledge/${id}/spans${qs}`);
|
|
}
|
|
|
|
export function delKnowledgeDetails(id: string) {
|
|
return del(`/api/v1/knowledge/${id}`);
|
|
}
|
|
|
|
// 批量删除(同一知识库内)。后端会校验所有 id 隶属于 kb_id 且具有编辑权限。
|
|
export function batchDeleteKnowledge(kbId: string, ids: string[]) {
|
|
return post(`/api/v1/knowledge/batch-delete`, { kb_id: kbId, ids });
|
|
}
|
|
|
|
export function downKnowledgeDetails(id: string) {
|
|
return getDown(`/api/v1/knowledge/${id}/download`);
|
|
}
|
|
|
|
export function previewKnowledgeFile(id: string) {
|
|
return getDown(`/api/v1/knowledge/${id}/preview`);
|
|
}
|
|
|
|
/** @param idsQueryString - query string with ids (e.g. ids=xxx&ids=yyy) */
|
|
export function batchQueryKnowledge(idsQueryString: string, kbId?: string, agentId?: string) {
|
|
let qs = idsQueryString;
|
|
if (kbId) qs += `&kb_id=${encodeURIComponent(kbId)}`;
|
|
if (agentId) qs += `&agent_id=${encodeURIComponent(agentId)}`;
|
|
return get(`/api/v1/knowledge/batch?${qs}`);
|
|
}
|
|
|
|
export function getKnowledgeDetailsCon(id: string, page: number) {
|
|
return get(`/api/v1/chunks/${id}?page=${page}&page_size=25`);
|
|
}
|
|
|
|
// Get chunk by chunk_id only (new endpoint - to be added to backend)
|
|
export function getChunkByIdOnly(chunkId: string) {
|
|
return get(`/api/v1/chunks/by-id/${chunkId}`);
|
|
}
|
|
|
|
// Delete a single generated question from a chunk by question ID
|
|
export function deleteGeneratedQuestion(chunkId: string, questionId: string) {
|
|
return del(`/api/v1/chunks/by-id/${chunkId}/questions`, { question_id: questionId });
|
|
}
|
|
|
|
export function listKnowledgeTags(
|
|
kbId: string,
|
|
params?: { page?: number; page_size?: number; keyword?: string },
|
|
) {
|
|
const query = buildQuery(params);
|
|
return get(`/api/v1/knowledge-bases/${kbId}/tags${query}`);
|
|
}
|
|
|
|
export function createKnowledgeBaseTag(
|
|
kbId: string,
|
|
data: { name: string; color?: string; sort_order?: number },
|
|
) {
|
|
return post(`/api/v1/knowledge-bases/${kbId}/tags`, data);
|
|
}
|
|
|
|
export function updateKnowledgeBaseTag(
|
|
kbId: string,
|
|
tagId: string,
|
|
data: { name?: string; color?: string; sort_order?: number },
|
|
) {
|
|
return put(`/api/v1/knowledge-bases/${kbId}/tags/${tagId}`, data);
|
|
}
|
|
|
|
export function deleteKnowledgeBaseTag(kbId: string, tagSeqId: number, params?: { force?: boolean }) {
|
|
const forceQuery = params?.force ? '?force=true' : '';
|
|
return del(`/api/v1/knowledge-bases/${kbId}/tags/${tagSeqId}${forceQuery}`);
|
|
}
|
|
|
|
export function updateKnowledgeTagBatch(data: { updates: Record<string, string | null> }) {
|
|
return put(`/api/v1/knowledge/tags`, data);
|
|
}
|
|
|
|
export function updateFAQEntryTagBatch(kbId: string, data: { updates: Record<number, number | null> }) {
|
|
return put(`/api/v1/knowledge-bases/${kbId}/faq/entries/tags`, data);
|
|
}
|
|
|
|
const buildQuery = (params?: Record<string, any>) => {
|
|
if (!params) return '';
|
|
const query = new URLSearchParams();
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value === undefined || value === null || value === '') return;
|
|
query.append(key, String(value));
|
|
});
|
|
const queryString = query.toString();
|
|
return queryString ? `?${queryString}` : '';
|
|
};
|
|
|
|
export function listFAQEntries(
|
|
kbId: string,
|
|
params?: { page?: number; page_size?: number; tag_id?: number; keyword?: string },
|
|
) {
|
|
const query = buildQuery(params);
|
|
return get(`/api/v1/knowledge-bases/${kbId}/faq/entries${query}`);
|
|
}
|
|
|
|
export function upsertFAQEntries(kbId: string, data: { entries: any[]; mode: 'append' | 'replace' }) {
|
|
return post(`/api/v1/knowledge-bases/${kbId}/faq/entries`, data);
|
|
}
|
|
|
|
export function createFAQEntry(kbId: string, data: any) {
|
|
return post(`/api/v1/knowledge-bases/${kbId}/faq/entry`, data);
|
|
}
|
|
|
|
export function updateFAQEntry(kbId: string, entryId: number, data: any) {
|
|
return put(`/api/v1/knowledge-bases/${kbId}/faq/entries/${entryId}`, data);
|
|
}
|
|
|
|
// Unified batch update API - supports is_enabled, is_recommended, tag_id
|
|
// Supports two modes:
|
|
// 1. By entry ID: use by_id field
|
|
// 2. By Tag: use by_tag field to apply the same update to all entries under a tag
|
|
export interface FAQEntryFieldsUpdate {
|
|
is_enabled?: boolean
|
|
is_recommended?: boolean
|
|
tag_id?: number | null
|
|
}
|
|
|
|
export interface FAQEntryFieldsBatchRequest {
|
|
by_id?: Record<number, FAQEntryFieldsUpdate>
|
|
by_tag?: Record<number, FAQEntryFieldsUpdate>
|
|
exclude_ids?: number[]
|
|
}
|
|
|
|
export function updateFAQEntryFieldsBatch(kbId: string, data: FAQEntryFieldsBatchRequest) {
|
|
return put(`/api/v1/knowledge-bases/${kbId}/faq/entries/fields`, data);
|
|
}
|
|
|
|
export function deleteFAQEntries(kbId: string, ids: number[]) {
|
|
return del(`/api/v1/knowledge-bases/${kbId}/faq/entries`, { ids });
|
|
}
|
|
|
|
export function searchFAQEntries(
|
|
kbId: string,
|
|
data: {
|
|
query_text: string
|
|
vector_threshold?: number
|
|
match_count?: number
|
|
}
|
|
) {
|
|
return post(`/api/v1/knowledge-bases/${kbId}/faq/search`, data);
|
|
}
|
|
|
|
// Export FAQ entries as CSV file
|
|
export async function exportFAQEntries(kbId: string): Promise<Blob> {
|
|
const response = await getDown(`/api/v1/knowledge-bases/${kbId}/faq/entries/export`);
|
|
return response as unknown as Blob;
|
|
}
|
|
|
|
// FAQ Import Progress API
|
|
export interface FAQBlockedEntry {
|
|
index: number
|
|
standard_question: string
|
|
reason: string
|
|
}
|
|
|
|
export interface FAQSuccessEntry {
|
|
index: number
|
|
seq_id: number
|
|
tag_id?: number
|
|
tag_name?: string
|
|
standard_question: string
|
|
}
|
|
|
|
export interface FAQImportProgress {
|
|
task_id: string
|
|
kb_id: string
|
|
knowledge_id: string
|
|
status: 'pending' | 'processing' | 'completed' | 'failed'
|
|
progress: number
|
|
total: number
|
|
processed: number
|
|
blocked: number
|
|
blocked_entries?: FAQBlockedEntry[]
|
|
success_entries?: FAQSuccessEntry[]
|
|
message: string
|
|
error: string
|
|
created_at: number
|
|
updated_at: number
|
|
}
|
|
|
|
export function getFAQImportProgress(taskId: string) {
|
|
return get(`/api/v1/faq/import/progress/${taskId}`);
|
|
}
|
|
|
|
export function updateFAQImportResultDisplayStatus(knowledgeBaseId: string, displayStatus: 'open' | 'close') {
|
|
return put(`/api/v1/knowledge-bases/${knowledgeBaseId}/faq/import/last-result/display`, {
|
|
display_status: displayStatus
|
|
});
|
|
}
|
|
|
|
export function searchKnowledge(
|
|
keyword: string,
|
|
offset = 0,
|
|
limit = 20,
|
|
fileTypes?: string[],
|
|
options?: { agent_id?: string; recent?: boolean }
|
|
) {
|
|
const query = new URLSearchParams();
|
|
if (keyword) {
|
|
query.set('keyword', keyword);
|
|
}
|
|
query.set('offset', String(offset));
|
|
query.set('limit', String(limit));
|
|
if (fileTypes && fileTypes.length > 0) {
|
|
query.set('file_types', fileTypes.join(','));
|
|
}
|
|
if (options?.agent_id) query.set('agent_id', options.agent_id);
|
|
if (options?.recent) query.set('recent', 'true');
|
|
return get(`/api/v1/knowledge/search?${query.toString()}`);
|
|
}
|
|
|
|
export function knowledgeSemanticSearch(data: {
|
|
query: string;
|
|
knowledge_base_ids?: string[];
|
|
knowledge_ids?: string[];
|
|
}) {
|
|
return post('/api/v1/knowledge-search', data);
|
|
}
|
|
|
|
export function batchReparseKnowledge(kbId: string, ids: string[], processConfig?: KnowledgeProcessOverrides) {
|
|
return post(`/api/v1/knowledge/batch-reparse`, {
|
|
kb_id: kbId,
|
|
ids,
|
|
process_config: processConfig,
|
|
});
|
|
} |