feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,257 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { cloneDeep } from 'lodash-es';
import { type ILevelSegment } from '@coze-data/knowledge-stores';
import { I18n } from '@coze-arch/i18n';
import { type LevelDocumentTree } from '../types';
export const getTreeNodes = (
segments: ILevelSegment[],
): LevelDocumentTree[] => {
const root = segments.find(f => f.parent === -1 && f.type === 'title');
if (!root) {
return segments.map(item => ({
...item,
id: item.id.toString(),
parent: item.parent?.toString(),
children: [],
}));
}
return [
{
...root,
id: root.id?.toString(),
parent: root.parent?.toString(),
children: getChildren(root, segments),
},
];
};
/** Segments to TreeNodes */
const getChildren = (
target: ILevelSegment,
list: ILevelSegment[],
): LevelDocumentTree[] =>
(target.children ?? []).reduce<LevelDocumentTree[]>((acc, cur) => {
const found = list.find(f => f.id === cur);
if (found) {
return [
...acc,
{
...found,
id: found.id?.toString(),
parent: found.parent?.toString(),
children: getChildren(found, list),
},
];
} else {
return [...acc];
}
}, []);
/**TreeNodes related */
export const findDescendantIDs = (node: LevelDocumentTree) => {
const ids = new Set<string>();
const findChild = (item: LevelDocumentTree) => {
if (!item || !item.id) {
return;
}
const { children } = item;
if (children && children.length) {
children.forEach(child => {
if (child && child.id) {
ids.add(child.id);
findChild(child);
}
});
}
};
findChild(node);
return ids;
};
export const findTreeNodeByID = (
nodes: LevelDocumentTree[],
id: string,
): LevelDocumentTree | null => {
for (const node of nodes) {
if (node.id === id) {
return node;
}
if (node.children) {
const found = findTreeNodeByID(node.children, id);
if (found) {
return found;
}
}
}
return null;
};
export const handleTreeNodeMove = (
positions: { dragIDs: string[]; parentID: string | null; dropIndex: number },
segments: ILevelSegment[],
): {
segments: ILevelSegment[] | null;
errMsg: string | null;
} => {
if (positions.parentID === null) {
return {
segments: null,
errMsg: I18n.t('knowledge_hierarchies_categories_01'),
};
}
const resSegments = cloneDeep(segments);
for (const id of positions.dragIDs) {
const dragSegmentIdx = resSegments.findIndex(
segment => segment.id.toString() === id,
);
if (dragSegmentIdx === -1) {
continue;
}
const dragSegment = resSegments[dragSegmentIdx];
const parentSegment = resSegments.find(
segment => segment.id.toString() === positions.parentID,
);
if (!parentSegment) {
return {
segments: null,
errMsg: I18n.t('knowledge_hierarchies_categories_02'),
};
}
// 如果是同一个 parent且拖动的位置在当前位置之前dropIndex 减 1
const originalIndex = parentSegment.children.indexOf(dragSegment.id);
const dropIndex =
originalIndex < positions.dropIndex &&
dragSegment.parent === parentSegment.id
? positions.dropIndex - 1
: positions.dropIndex;
if (dragSegment.parent !== parentSegment.id) {
// Remove from old parent's children
const oldParent = resSegments.find(s => s.id === dragSegment.parent);
oldParent?.children.splice(oldParent.children.indexOf(dragSegment.id), 1);
dragSegment.parent = parentSegment.id;
}
// Reorder in parent's children
parentSegment.children = parentSegment.children.filter(
child => child !== dragSegment.id,
);
parentSegment.children.splice(dropIndex, 0, dragSegment.id);
}
return {
segments: resSegments,
errMsg: null,
};
};
export const handleDeleteNode = (ids: string[], segments: ILevelSegment[]) => {
const resSegments = cloneDeep(segments);
for (const id of ids) {
const index = resSegments.findIndex(item => item.id.toString() === id);
const parentSegment = resSegments.find(
item => item.id === resSegments[index].parent,
);
if (parentSegment) {
parentSegment.children = parentSegment.children.filter(
item => item !== resSegments[index].id,
);
}
resSegments.splice(index, 1);
}
return resSegments;
};
export const handleMergeNodes = (
id: string,
descendants: string[],
segments: ILevelSegment[],
): {
segments: ILevelSegment[] | null;
errMsg: string | null;
} => {
const resSegments = cloneDeep(segments);
const mergedSegment = resSegments.find(item => item.id.toString() === id);
if (!mergedSegment) {
return {
segments: null,
errMsg: I18n.t('knowledge_hierarchies_categories_03'),
};
}
if (mergedSegment.parent === -1 && mergedSegment.type === 'title') {
return {
segments: null,
errMsg: I18n.t('knowledge_hierarchies_categories_03'),
};
}
mergedSegment.children = [];
mergedSegment.type = 'section-text';
for (const descendant of descendants) {
const segmentToMerge = resSegments.find(
item => item.id.toString() === descendant,
);
if (!segmentToMerge) {
return {
segments: null,
errMsg: I18n.t('knowledge_hierarchies_categories_04'),
};
}
// 从原父节点的 children 中移除该节点
const parentSegment = resSegments.find(
item => item.id === segmentToMerge.parent,
);
if (parentSegment) {
parentSegment.children = parentSegment.children.filter(
childId => childId !== segmentToMerge.id,
);
}
if (!['table', 'image', 'title'].includes(segmentToMerge?.type ?? '')) {
// 合并文本内容并删除节点
mergedSegment.text += segmentToMerge.text;
const index = resSegments.findIndex(
item => item.id === segmentToMerge.id,
);
if (index !== -1) {
resSegments.splice(index, 1);
}
} else {
// 非section-text类型的节点将其移动到合并后节点的children中
segmentToMerge.parent = mergedSegment.id;
mergedSegment.children.push(segmentToMerge.id);
}
}
return {
segments: resSegments,
errMsg: null,
};
};