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,35 @@
/*
* 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 BigNumber from 'bignumber.js';
/**
* 是不是大数字
* @param value
* @returns
*/
export function isBigNumber(value: unknown): value is BigNumber {
return !!(value && value instanceof BigNumber);
}
/**
* 大数字转字符串
* @param value
* @returns
*/
export function bigNumbertoString(value: BigNumber): string {
return value.toFixed();
}

View File

@@ -0,0 +1,104 @@
/*
* 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 { isObject } from 'lodash-es';
import { LineStatus, type JsonValueType, type Field } from '../types';
import { isBigNumber } from './big-number';
/**
* 通过父元素的线条状态推到子元素的线条状态
*/
const getLineByParent2Child = (pLine: LineStatus): LineStatus => {
switch (pLine) {
/** 表示父节点也是从父父节点下钻而来,此处的子节点只需要把线延续下去即可 */
case LineStatus.Visible:
return LineStatus.Half;
/** 表示父节点是父父节点的最后一个节点,子节点无需再延续,渲染空白即可 */
case LineStatus.Last:
return LineStatus.Hidden;
/** 其他的情况完全继承父节点的线 */
default:
return pLine;
}
};
/**
* 将 object 解析成可以循环渲染的 fields
* 1. 若 object 非复杂类型,则返回长度为 1 的 fields 只渲染一项
* 2. 若 object = {},则返回长度为 0 的 fields渲染层需要做好兜底
*/
const generateFields = (object: JsonValueType): Field[] => {
/** 若 object 非复杂类型 */
if (!isObject(object) || isBigNumber(object)) {
return [
{
path: [],
lines: [],
value: object,
isObj: false,
children: [],
},
];
}
/** 递归计算时缓存一下计算好的线,没别的意义,降低一些时间复杂度 */
const lineMap = new Map<string[], LineStatus[]>();
/** 递归解析 object 为 fields */
const dfs = ($object: object, $parentPath: string[] = []): Field[] => {
// 如果不是对象,直接返回空数组,兜底异常情况
if (!isObject($object)) {
return [];
}
// 如果是大数字,直接返回空数组
if (isBigNumber($object)) {
return [];
}
const parentLines = lineMap.get($parentPath) || [];
const keys = Object.keys($object);
return keys.map((key, idx) => {
const value = $object[key];
const path = $parentPath.concat(key);
const last = idx === keys.length - 1;
/**
* 根据父节点的线推导子节点的线
*/
const lines = parentLines
.map<LineStatus>(getLineByParent2Child)
/**
* 最后拼接上子节点自己的线,最后一个节点和普通的节点有样式区分
*/
.concat(last ? LineStatus.Last : LineStatus.Visible);
lineMap.set(path, lines);
return {
path,
lines,
value,
children: dfs(value, path),
isObj: isObject(value) && !isBigNumber(value),
};
});
};
return dfs(object);
};
export { generateFields };

View File

@@ -0,0 +1,33 @@
/*
* 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.
*/
// 对象中的文本,避免字符被转译
export const generateStrAvoidEscape = (str: string) => {
const characters = {
'\\': '\\\\',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
};
let next = '';
for (let i = 0; i < str.length; i++) {
const char = str[i];
next += characters[char] || char;
}
return next;
};

View File

@@ -0,0 +1,79 @@
/*
* 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 { type ReactNode } from 'react';
import { isString } from 'lodash-es';
import { Typography } from '@coze-arch/bot-semi';
import { generateStrAvoidEscape } from './generate-str-avoid-escape';
const { Text } = Typography;
export const generateStr2Link = (str: string, avoidEscape?: boolean) => {
if (str === '') {
return [''];
}
if (avoidEscape) {
str = generateStrAvoidEscape(str);
}
/**
* 更严格的 url 匹配规则,防止过度匹配
* 协议http、https
* 域名:允许使用 -、a-z、A-Z、0-9其中 - 不能开头,每一级域名长度不会超过 63
* 端口:支持带端口 0 - 65535
* URL严格型不匹配中文等转译前的文字否则一旦命中将会识别整段字符串
*/
const urlReg = new RegExp(
'http(s)?://' +
'[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+' +
'(:[0-9]{1,5})?' +
'[-a-zA-Z0-9()@:%_\\+.~#?&//=]*',
'g',
);
const matches = [...str.matchAll(urlReg)];
/**
* 切割字符串url 嵌套为 link 的样式,切割步骤:
* 1. 匹配字符串中所有的 url
* 2. 倒序 matches从末尾开始切原因是 match.index 是从头开始计数,从头切增加计算量
* 3. 每一个 match 切两刀成三段,头尾是普通字符串,中间为 url
* 4. 按照 end、url、start 的顺序 push 到栈中,下次 match 会直接取 start 继续切
* 5. 切割完成后做一次倒序
*/
return matches
.reverse()
.reduce<ReactNode[]>(
(nodes, match) => {
const lastNode = nodes.pop();
if (!isString(lastNode)) {
return nodes.concat(lastNode);
}
const startIdx = match.index || 0;
const endIdx = startIdx + match[0].length;
const startStr = lastNode.slice(0, startIdx);
const endStr = lastNode.slice(endIdx);
return nodes.concat(
endStr,
<Text link={{ href: match[0], target: '_blank' }}>{match[0]}</Text>,
startStr,
);
},
[str],
)
.reverse();
};

View File

@@ -0,0 +1,19 @@
/*
* 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.
*/
export { generateFields } from './generate-field';
export { generateStr2Link } from './generate-str-to-link';
export { generateStrAvoidEscape } from './generate-str-avoid-escape';