feat: 知识库文件夹/模板、技能压缩设置与摘要窗口调优

- 知识库新增文件夹与模板支持(模型/仓储/服务/迁移),前端知识管理组件
- 岗位管理模型与接口完善
- 技能新增压缩设置页与相关存储/工具能力
- 系统设置扩展配置项
- 摘要中间件 keep 窗口改用 token 预算(16000),减少重复压缩

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.8 committed 2026-06-16 15:23:54 +08:00
1 parent e6662cf673
commit 5ebbb82d66
54 files changed
+2658 -100

No files matched your search

+111 -1
View File
@@ -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",
}),
);
}
+68 -1
View File
@@ -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 }),
};
}
+37
View File
@@ -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 {