feat: 知识库文件夹/模板、技能压缩设置与摘要窗口调优
- 知识库新增文件夹与模板支持(模型/仓储/服务/迁移),前端知识管理组件 - 岗位管理模型与接口完善 - 技能新增压缩设置页与相关存储/工具能力 - 系统设置扩展配置项 - 摘要中间件 keep 窗口改用 token 预算(16000),减少重复压缩 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e6662cf673
commit
5ebbb82d66
54 files changed
+2658
-100
No files matched your search
@@ -4,8 +4,11 @@ import { getBackendBaseURL } from "@/core/config";
|
||||
import type {
|
||||
BatchIngestPayload,
|
||||
BatchIngestResult,
|
||||
ExtractTemplateScope,
|
||||
GraphExportResult,
|
||||
IngestThreadPayload,
|
||||
KnowledgeExtractTemplate,
|
||||
KnowledgeFolder,
|
||||
KnowledgeListParams,
|
||||
KnowledgeListResponse,
|
||||
KnowledgeNote,
|
||||
@@ -35,6 +38,7 @@ export async function listKnowledgeNotes(params: KnowledgeListParams = {}) {
|
||||
if (params.entity) qs.set("entity", params.entity);
|
||||
if (params.source_type) qs.set("source_type", params.source_type);
|
||||
if (params.status) qs.set("status", params.status);
|
||||
if (params.folder !== undefined) qs.set("folder", params.folder);
|
||||
qs.set("limit", String(params.limit ?? 20));
|
||||
qs.set("offset", String(params.offset ?? 0));
|
||||
return parseResponse<KnowledgeListResponse>(
|
||||
@@ -54,6 +58,7 @@ export async function createKnowledgeNote(data: {
|
||||
summary?: string | null;
|
||||
tags?: string[];
|
||||
status?: string;
|
||||
folder?: string | null;
|
||||
}) {
|
||||
const res = await parseResponse<{ note: KnowledgeNote }>(
|
||||
await apiFetch(`${baseUrl()}/notes`, {
|
||||
@@ -73,6 +78,7 @@ export async function updateKnowledgeNote(
|
||||
summary: string | null;
|
||||
tags: string[];
|
||||
status: string;
|
||||
folder: string | null;
|
||||
}>,
|
||||
) {
|
||||
return parseResponse<KnowledgeNote>(
|
||||
@@ -138,7 +144,14 @@ export interface IngestFileResult {
|
||||
/** Import a file (md/txt/pdf/word/ppt/excel) into the KB as wiki note(s). */
|
||||
export async function ingestKnowledgeFile(
|
||||
file: File,
|
||||
opts: { title?: string; tags?: string; status?: string; model_name?: string | null } = {},
|
||||
opts: {
|
||||
title?: string;
|
||||
tags?: string;
|
||||
status?: string;
|
||||
model_name?: string | null;
|
||||
template_id?: string | null;
|
||||
folder?: string | null;
|
||||
} = {},
|
||||
) {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
@@ -146,6 +159,8 @@ export async function ingestKnowledgeFile(
|
||||
if (opts.tags) form.append("tags", opts.tags);
|
||||
if (opts.status) form.append("status", opts.status);
|
||||
if (opts.model_name) form.append("model_name", opts.model_name);
|
||||
if (opts.template_id) form.append("template_id", opts.template_id);
|
||||
if (opts.folder) form.append("folder", opts.folder);
|
||||
// No Content-Type header — the browser sets the multipart boundary itself.
|
||||
return parseResponse<IngestFileResult>(
|
||||
await apiFetch(`${baseUrl()}/ingest/file`, { method: "POST", body: form }),
|
||||
@@ -284,3 +299,98 @@ export async function removeGraphBlacklist(entryId: string) {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// --- folders (user-managed directories) ---
|
||||
|
||||
export async function listKnowledgeFolders() {
|
||||
const res = await parseResponse<{ items: KnowledgeFolder[] }>(
|
||||
await apiFetch(`${baseUrl()}/folders`),
|
||||
);
|
||||
return res.items;
|
||||
}
|
||||
|
||||
export async function createKnowledgeFolder(path: string) {
|
||||
const res = await parseResponse<{ folder: KnowledgeFolder }>(
|
||||
await apiFetch(`${baseUrl()}/folders`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ path }),
|
||||
}),
|
||||
);
|
||||
return res.folder;
|
||||
}
|
||||
|
||||
export async function renameKnowledgeFolder(folderId: string, path: string) {
|
||||
const res = await parseResponse<{ folder: KnowledgeFolder }>(
|
||||
await apiFetch(`${baseUrl()}/folders/${encodeURIComponent(folderId)}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ path }),
|
||||
}),
|
||||
);
|
||||
return res.folder;
|
||||
}
|
||||
|
||||
export async function deleteKnowledgeFolder(folderId: string, reassignTo?: string) {
|
||||
const qs = reassignTo ? `?reassign_to=${encodeURIComponent(reassignTo)}` : "";
|
||||
return parseResponse<{ ok: boolean }>(
|
||||
await apiFetch(`${baseUrl()}/folders/${encodeURIComponent(folderId)}${qs}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// --- extraction prompt templates ---
|
||||
|
||||
export async function listExtractTemplates(scope?: ExtractTemplateScope, enabledOnly = false) {
|
||||
const qs = new URLSearchParams();
|
||||
if (scope) qs.set("scope", scope);
|
||||
if (enabledOnly) qs.set("enabled_only", "true");
|
||||
const q = qs.toString();
|
||||
const res = await parseResponse<{ items: KnowledgeExtractTemplate[] }>(
|
||||
await apiFetch(`${baseUrl()}/extract-templates${q ? `?${q}` : ""}`),
|
||||
);
|
||||
return res.items;
|
||||
}
|
||||
|
||||
export type ExtractTemplateInput = {
|
||||
name: string;
|
||||
system_prompt: string;
|
||||
scope?: ExtractTemplateScope;
|
||||
description?: string | null;
|
||||
is_default?: boolean;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
export async function createExtractTemplate(data: ExtractTemplateInput) {
|
||||
const res = await parseResponse<{ template: KnowledgeExtractTemplate }>(
|
||||
await apiFetch(`${baseUrl()}/extract-templates`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
);
|
||||
return res.template;
|
||||
}
|
||||
|
||||
export async function updateExtractTemplate(
|
||||
templateId: string,
|
||||
data: Partial<ExtractTemplateInput>,
|
||||
) {
|
||||
const res = await parseResponse<{ template: KnowledgeExtractTemplate }>(
|
||||
await apiFetch(`${baseUrl()}/extract-templates/${encodeURIComponent(templateId)}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}),
|
||||
);
|
||||
return res.template;
|
||||
}
|
||||
|
||||
export async function deleteExtractTemplate(templateId: string) {
|
||||
return parseResponse<{ ok: boolean }>(
|
||||
await apiFetch(`${baseUrl()}/extract-templates/${encodeURIComponent(templateId)}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
|
||||
import {
|
||||
addGraphBlacklist,
|
||||
createExtractTemplate,
|
||||
createKnowledgeFolder,
|
||||
createKnowledgeNote,
|
||||
deleteExtractTemplate,
|
||||
deleteKnowledgeFolder,
|
||||
deleteKnowledgeNote,
|
||||
exportKnowledgeGraph,
|
||||
fetchVaultPage,
|
||||
@@ -18,14 +22,18 @@ import {
|
||||
ingestThread,
|
||||
ingestThreadsBatch,
|
||||
listDuplicates,
|
||||
listExtractTemplates,
|
||||
listKnowledgeFolders,
|
||||
listKnowledgeNotes,
|
||||
mergeKnowledgeNotes,
|
||||
removeGraphBlacklist,
|
||||
renameKnowledgeFolder,
|
||||
resolveNoteWikilinks,
|
||||
searchKnowledge,
|
||||
updateExtractTemplate,
|
||||
updateKnowledgeNote,
|
||||
} from "./api";
|
||||
import type { KnowledgeListParams } from "./types";
|
||||
import type { ExtractTemplateScope, KnowledgeListParams } from "./types";
|
||||
|
||||
const ROOT_KEY = ["knowledge"] as const;
|
||||
|
||||
@@ -172,3 +180,62 @@ export function useKnowledgeDuplicates(enabled: boolean) {
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
// --- folders (user-managed directories) ---
|
||||
|
||||
export function useKnowledgeFolders() {
|
||||
return useQuery({
|
||||
queryKey: [...ROOT_KEY, "folders"],
|
||||
queryFn: listKnowledgeFolders,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useFolderActions() {
|
||||
const queryClient = useQueryClient();
|
||||
// Folder changes can move notes around → invalidate the whole KB tree.
|
||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: ROOT_KEY });
|
||||
return {
|
||||
create: useMutation({ mutationFn: createKnowledgeFolder, onSuccess: invalidate }),
|
||||
rename: useMutation({
|
||||
mutationFn: ({ folderId, path }: { folderId: string; path: string }) =>
|
||||
renameKnowledgeFolder(folderId, path),
|
||||
onSuccess: invalidate,
|
||||
}),
|
||||
remove: useMutation({
|
||||
mutationFn: ({ folderId, reassignTo }: { folderId: string; reassignTo?: string }) =>
|
||||
deleteKnowledgeFolder(folderId, reassignTo),
|
||||
onSuccess: invalidate,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// --- extraction prompt templates ---
|
||||
|
||||
export function useExtractTemplates(scope?: ExtractTemplateScope, enabledOnly = false) {
|
||||
return useQuery({
|
||||
queryKey: [...ROOT_KEY, "extract-templates", scope ?? "all", enabledOnly],
|
||||
queryFn: () => listExtractTemplates(scope, enabledOnly),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useExtractTemplateActions() {
|
||||
const queryClient = useQueryClient();
|
||||
const invalidate = () =>
|
||||
queryClient.invalidateQueries({ queryKey: [...ROOT_KEY, "extract-templates"] });
|
||||
return {
|
||||
create: useMutation({ mutationFn: createExtractTemplate, onSuccess: invalidate }),
|
||||
update: useMutation({
|
||||
mutationFn: ({
|
||||
templateId,
|
||||
data,
|
||||
}: {
|
||||
templateId: string;
|
||||
data: Parameters<typeof updateExtractTemplate>[1];
|
||||
}) => updateExtractTemplate(templateId, data),
|
||||
onSuccess: invalidate,
|
||||
}),
|
||||
remove: useMutation({ mutationFn: deleteExtractTemplate, onSuccess: invalidate }),
|
||||
};
|
||||
}
|
||||
@@ -31,6 +31,8 @@ export interface KnowledgeNote {
|
||||
summary?: string | null;
|
||||
content_md: string;
|
||||
vault_path?: string | null;
|
||||
/** User-assigned directory (slash-joined path); null/undefined = unfiled. */
|
||||
folder?: string | null;
|
||||
source_type: string;
|
||||
source_id?: string | null;
|
||||
status: KnowledgeStatus;
|
||||
@@ -58,10 +60,37 @@ export interface KnowledgeListParams {
|
||||
entity?: string;
|
||||
source_type?: string;
|
||||
status?: string;
|
||||
/** Filter to a directory (and its sub-folders); "" = unfiled only. */
|
||||
folder?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
/** A user-managed knowledge directory. */
|
||||
export interface KnowledgeFolder {
|
||||
id: string;
|
||||
path: string;
|
||||
name: string;
|
||||
sort_order?: number;
|
||||
}
|
||||
|
||||
export type ExtractTemplateScope = "thread" | "document" | "both";
|
||||
|
||||
/** An editable knowledge-extraction prompt template. */
|
||||
export interface KnowledgeExtractTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
scope: ExtractTemplateScope;
|
||||
system_prompt: string;
|
||||
is_default: boolean;
|
||||
enabled: boolean;
|
||||
created_by?: string | null;
|
||||
updated_by?: string | null;
|
||||
created_at?: string | null;
|
||||
updated_at?: string | null;
|
||||
}
|
||||
|
||||
export type WikilinkKind = "note" | "vault" | "missing";
|
||||
|
||||
export interface WikilinkResolution {
|
||||
@@ -105,6 +134,10 @@ export interface IngestThreadPayload {
|
||||
background?: boolean;
|
||||
/** Optional model override for knowledge extraction (null = configured default). */
|
||||
model_name?: string | null;
|
||||
/** Optional extraction-prompt template id (null = scope default). */
|
||||
template_id?: string | null;
|
||||
/** Optional directory to file the result under (slash-joined path). */
|
||||
folder?: string | null;
|
||||
}
|
||||
|
||||
export interface BatchIngestPayload {
|
||||
@@ -116,6 +149,10 @@ export interface BatchIngestPayload {
|
||||
mode?: "summary" | "full";
|
||||
/** Optional model override for knowledge extraction (null = configured default). */
|
||||
model_name?: string | null;
|
||||
/** Optional extraction-prompt template id (null = scope default). */
|
||||
template_id?: string | null;
|
||||
/** Optional directory to file results under (slash-joined path). */
|
||||
folder?: string | null;
|
||||
}
|
||||
|
||||
export interface BatchIngestResult {
|
||||
|
||||
Reference in new issue
Block a user