chore: replace all cn comments of fe to en version by volc api (#320)
This commit is contained in:
@@ -22,7 +22,7 @@ export enum BotE2e {
|
||||
BotListIconDel = 'bot.list.delete.icon',
|
||||
BotListIconEdit = 'bot.list.edit.icon',
|
||||
BotKnowledgeAutoMaticBtn = 'bot.knowledge.auto_matic.btn',
|
||||
/** 知识库设置弹框 */
|
||||
/** Knowledge base settings pop-up box */
|
||||
BotKnowledgeSettingModalTitle = 'bot.knowledge.setting.modal.title.text',
|
||||
BotKnowledgeSettingModalAutoRadio = 'bot.knowledge.setting.modal.auto.radio',
|
||||
BotKnowledgeSettingModalManualRadio = 'bot.knowledge.setting.modal.manual.radio',
|
||||
@@ -32,7 +32,7 @@ export enum BotE2e {
|
||||
BotKnowledgeSettingShowSourceDisplaySwitch = 'bot.knowledge.setting.show.source.display.switch',
|
||||
BotKnowledgeSettingShowSourceModeCardRadio = 'bot.knowledge.setting.show.source.mode.cardlist.radio',
|
||||
BotKnowledgeSettingShowSourceModeTextRadio = 'bot.knowledge.setting.show.source.mode.replybottom.radio',
|
||||
/** 选择知识库列表 */
|
||||
/** Select Knowledge Base List */
|
||||
BotKnowledgeSelectListModalCreateBtn = 'bot.knowledge.select.list.modal.create.btn',
|
||||
BotKnowledgeSelectListModalCreateDateSelect = 'bot.knowledge.select.list.modal.create.date.select',
|
||||
BotKnowledgeSelectListModalAllTab = 'bot.knowledge.select.list.modal.all.tab',
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
export enum KnowledgeE2e {
|
||||
KnowledgeTab = 'knowledge.tab',
|
||||
// 知识库列表
|
||||
// Knowledge Base List
|
||||
// KnowledgeSearch = 'ui.search_input',
|
||||
KnowledgeSearchType = 'knowledge.list.search.type.select',
|
||||
KnowledgeSearchFounder = 'knowledge.list.search.founder.select',
|
||||
KnowledgeCreateBtn = 'knowledge.list.create.btn',
|
||||
/** 以ui前缀开头的是原来就打过的标,列表页需要根据index去定位 */
|
||||
/** The one starting with the ui prefix is the original mark, and the list page needs to be positioned according to the index. */
|
||||
// KnowledgeListName = 'knowledge.list.name.text',
|
||||
// KnowledgeListIcon = 'ui.table-meta',
|
||||
// KnowledgeListIconDel = 'ui.table-action.delete',
|
||||
@@ -32,7 +32,7 @@ export enum KnowledgeE2e {
|
||||
KnowledgeListTableTab = 'knowledge.list.table.tab',
|
||||
KnowledgeEditModalDescInput = 'knowledge.edit.modal.desc.input',
|
||||
KnowledgeEditModalNameInput = 'knowledge.edit.modal.name.input',
|
||||
// 创建知识库弹框
|
||||
// Create a knowledge base popup
|
||||
CreateKnowledgeModal = 'knowledge.create.modal',
|
||||
CreateKnowledgeModalTitle = 'knowledge.create.modal.title.text',
|
||||
CreateKnowledgeModalTextRadioGroup = 'knowledge.create.modal.text.RadioGroup',
|
||||
@@ -57,9 +57,9 @@ export enum KnowledgeE2e {
|
||||
CreateKnowledgeModalTextLarkRadio = 'knowledge.create.modal.text.lark.radio',
|
||||
CreateKnowledgeModalTableLarkRadio = 'knowledge.create.modal.table.lark.radio',
|
||||
CreateKnowledgeModalSubmitAndImportButton = 'knowledge.create.modal.submit_and_import.button',
|
||||
// 创建文本类型
|
||||
// Create a text type
|
||||
CreateTextUploadNav = 'knowledge.create.text.upload.nav.text',
|
||||
/** 创建知识库步骤条 */
|
||||
/** Create a knowledge base step bar */
|
||||
// CreateKnowledgeStepUploadFile = 'knowledge.create.step.upload',
|
||||
// CreateKnowledgeStepSegment = 'knowledge.create.step.segment',
|
||||
// CreateKnowledgeStepProcess = 'knowledge.create.step.process',
|
||||
@@ -69,7 +69,7 @@ export enum KnowledgeE2e {
|
||||
UploadUnitAddBtn = 'knowledge.create.unit.add.btn',
|
||||
UploadUnitCancelBtn = 'knowledge.create.unit.cancel.btn',
|
||||
CreateUnitConfirmBtn = 'knowledge.create.unit.confirm.btn',
|
||||
/** 本地上传列表 */
|
||||
/** local upload list */
|
||||
LocalUploadListName = 'knowledge.upload.list.name.input',
|
||||
LocalUploadListNameView = 'knowledge.upload.list.name.text',
|
||||
LocalUploadListStatus = 'knowledge.upload.list.status.text',
|
||||
@@ -86,14 +86,14 @@ export enum KnowledgeE2e {
|
||||
CreateUnitListProgressName = 'knowledge.create.unit.progress.name.text',
|
||||
CreateUnitListProgressPercent = 'knowledge.create.unit.progress.percent.text',
|
||||
|
||||
/** 创建text-online类型(url) */
|
||||
/** Create a text-online type (url) */
|
||||
OnlineUploadAutoBtn = 'knowledge.create.text.online.auto.btn',
|
||||
OnlineUploadManualBtn = 'knowledge.create.text.online.manual.btn',
|
||||
OnlineUploadModal = 'knowledge.create.text.online.upload.modal',
|
||||
OnlineUploadModalAddTypeSelect = 'knowledge.create.text.online.upload.modal.add_type.select',
|
||||
OnlineUploadModalFrequencySelect = 'knowledge.create.text.online.upload.modal.frequency.select',
|
||||
OnlineUploadModalExampleUrlSelect = 'knowledge.create.text.online.upload.modal.example_url.input',
|
||||
/** 创建text-custom类型 */
|
||||
/** Create a text-custom type */
|
||||
CustomUploadNameInput = 'knowledge.create.custom.upload.name.input',
|
||||
SegmentEditor = 'knowledge.segment.editor',
|
||||
SegmentEditorInsertImgBtn = 'knowledge.segment.editor.insert_img.btn',
|
||||
@@ -131,14 +131,14 @@ export enum KnowledgeE2e {
|
||||
ImageAnnotationAllTab = 'knowledge.create.image.annotation.all.tab',
|
||||
ImageAnnotationUnAnnotationTab = 'knowledge.create.image.annotation.un_annotation.tab',
|
||||
ImageAnnotationAnnotationedTab = 'knowledge.create.image.annotation.annotationed.tab',
|
||||
/** 增量 */
|
||||
/** increment */
|
||||
IncrementTableUploadStructureTitle = 'knowledge.increment.table.upload.structure_title.text',
|
||||
IncrementTableUploadStructureColumnName = 'knowledge.increment.table.upload.structure_column_name.input',
|
||||
IncrementTableUploadStructureAddBtn = 'knowledge.increment.table.upload.structure_add.btn',
|
||||
|
||||
// CreateKnowledgeModalCancelBtn = 'knowledge.create.modal.cancel.btn',
|
||||
// CreateKnowledgeModalNextBtn = 'knowledge.create.modal.next.btn',
|
||||
/** segment详情页 */
|
||||
/** Segment details page */
|
||||
SegmentDetailUpdateBtn = 'knowledge.segment.detail.update.btn',
|
||||
SegmentDetailTitle = 'knowledge.segment.detail.title.text',
|
||||
SegmentDetailTitleEditIcon = 'knowledge.segment.detail.title.edit.icon',
|
||||
@@ -167,16 +167,16 @@ export enum KnowledgeE2e {
|
||||
SegmentDetailBatchFrequencyModalSelect = 'knowledge.segment.detail.batch_frequency.modal.select',
|
||||
SegmentDetailBatchFrequencyModalcheckboxAll = 'knowledge.segment.detail.batch_frequency.modal.checkbox_all',
|
||||
SegmentDetailBatchFrequencyModalcheckboxItem = 'knowledge.segment.detail.batch_frequency.modal.checkbox_item',
|
||||
/** segment详情页重新分段流程页 */
|
||||
/** Segment details page Re-segment process page */
|
||||
ResegmentUploadUnitNextBtn = 'knowledge.resegment.unit.next.btn',
|
||||
ResegmentUnitConfirmBtn = 'knowledge.resegment.unit.confirm.btn',
|
||||
/** knowledge通用 */
|
||||
/** Common knowledge */
|
||||
UnitDetailTags = 'knowledge.unit.detail.tags',
|
||||
UnitDetailTagsProcessing = 'knowledge.unit.detail.tags.processing.tag',
|
||||
UnitDetailTagsFailed = 'knowledge.unit.detail.tags.failed.tag',
|
||||
KnowledgeAddContentNavBar = 'knowledge.unit.add.content.navbar',
|
||||
}
|
||||
/** 底层组件 */
|
||||
/** underlying components */
|
||||
export const UIE2E = [
|
||||
'ui.select.option',
|
||||
'ui.table-action.delete',
|
||||
|
||||
@@ -38,6 +38,6 @@ class FeatureRegistryManager extends ExternalStore<
|
||||
}
|
||||
|
||||
/**
|
||||
* FeatureRegistryManager 的实例,用于注册和注销 FeatureRegistry。开发过程中 FeatureRegistry 初始化的时候会写入到这个实例中,方便调试。
|
||||
* FeatureRegistryManager instance for registering and unregistering FeatureRegistry. It will be written to this instance when FeatureRegistry is initialized during development, which is convenient for debugging.
|
||||
*/
|
||||
export const featureRegistryManager = new FeatureRegistryManager();
|
||||
|
||||
@@ -24,14 +24,14 @@ import { featureRegistryManager } from './feature-registry-manager';
|
||||
import { ExternalStore } from './external-store';
|
||||
|
||||
export type FeatureModule<Type, Module> = Module & {
|
||||
type: Type | string; // 默认给Module增加type字段, 方便作为组件的key
|
||||
type: Type | string; // By default, add the type field to the Module, which is convenient as the key of the component
|
||||
};
|
||||
|
||||
export interface FeatureConfig<Type, Module> {
|
||||
type: Type | string;
|
||||
module?: Module;
|
||||
loader?: () => Promise<{ default: Module }>;
|
||||
tags?: string[]; // 通常用于Feature分组
|
||||
tags?: string[]; // Usually Used for Feature Grouping
|
||||
}
|
||||
|
||||
export interface DefaultFeatureConfig<Type, Module>
|
||||
@@ -39,7 +39,7 @@ export interface DefaultFeatureConfig<Type, Module>
|
||||
module: Module;
|
||||
}
|
||||
|
||||
// 通过解析context获取当前应当使用的Feature类型
|
||||
// Get the Feature type that should be used by parsing the context
|
||||
export interface FeatureTypeParser<Type, Context> {
|
||||
(context: Context): Type | string;
|
||||
}
|
||||
@@ -207,7 +207,7 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
});
|
||||
}
|
||||
|
||||
// 调用Feature loader加载组件
|
||||
// Feature loader to load component
|
||||
async load(type: Type | string): Promise<void> {
|
||||
const feature = this.getFeature(type);
|
||||
if (!feature) {
|
||||
@@ -235,7 +235,7 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
});
|
||||
}
|
||||
|
||||
// 判断Feature是否已经加载完成
|
||||
// Determine whether the Feature has been loaded
|
||||
isLoaded(type: Type | string): boolean {
|
||||
const feature = this.getFeature(type);
|
||||
if (!feature) {
|
||||
@@ -252,16 +252,16 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
return true;
|
||||
}
|
||||
|
||||
// Feature是否注册
|
||||
// Features Registration
|
||||
has(type: Type | string): boolean {
|
||||
return Boolean(this.getFeature(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Feature Module
|
||||
* Get Feature Modules
|
||||
*
|
||||
* @param type Feature 类型
|
||||
* @returns 如果 Feature 不存在或者 Feature.Module 是空则返回 undefined
|
||||
* @param type Feature type
|
||||
* @Returns undefined if Feature does not exist or Feature. Module is empty
|
||||
*/
|
||||
getModule(type: Type | string): Module | undefined {
|
||||
const feature = this.getFeature(type);
|
||||
@@ -285,9 +285,9 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Feature Module
|
||||
* Get Feature Modules
|
||||
*
|
||||
* @deprecated【注意!】这个方法使用时要注意,因为它会导致 module 的 type 字段被覆盖,使用 `getModule()` 方法
|
||||
* @Deprecated [Note!] Be careful when using this method, because it will cause the type field of the module to be overwritten, use the'getModule () 'method
|
||||
*/
|
||||
get(type: Type | string): FeatureModule<Type, Module> | undefined {
|
||||
const feature = this.getFeature(type);
|
||||
@@ -329,7 +329,7 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有 Feature 模块的 entries,key 是 feature type,value 是 feature module
|
||||
* Get entries for all Feature modules, key is feature type, value is feature module
|
||||
*/
|
||||
entries(): [Type | string, Module][] {
|
||||
const { featureMap } = this;
|
||||
@@ -358,7 +358,7 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
>[];
|
||||
}
|
||||
|
||||
// 通过context获取对应的Feature类型
|
||||
// Get the corresponding Feature type through context
|
||||
getTypeByContext(context: Context): Type | string {
|
||||
const { featureTypeParser } = this;
|
||||
if (!featureTypeParser) {
|
||||
@@ -381,7 +381,7 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
}
|
||||
}
|
||||
|
||||
// 通过context获取对应的Feature模块
|
||||
// Get the corresponding Feature module through the context
|
||||
getByContext(context: Context): FeatureModule<Type, Module> | undefined {
|
||||
const type = this.getTypeByContext(context);
|
||||
return this.get(type);
|
||||
@@ -394,7 +394,7 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
return this.getAsync(type);
|
||||
}
|
||||
|
||||
// 获取默认Feature模块
|
||||
// Get Default Feature Module
|
||||
getDefault(): FeatureModule<Type, Module> | undefined {
|
||||
if (!this.defaultFeature) {
|
||||
return undefined;
|
||||
@@ -405,12 +405,12 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
};
|
||||
}
|
||||
|
||||
// 获取默认Feature类型
|
||||
// Get Default Feature Type
|
||||
getDefaultType(): Type | string | undefined {
|
||||
return this.defaultFeature?.type;
|
||||
}
|
||||
|
||||
// 设置默认Feature
|
||||
// Set Default Features
|
||||
setDefaultFeature(feature: DefaultFeatureConfig<Type, Module>) {
|
||||
const { type } = feature;
|
||||
const key = this.getFeatureKey(type);
|
||||
@@ -420,14 +420,14 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
});
|
||||
}
|
||||
|
||||
// 设置Feature类型解析函数
|
||||
// Setting Features Type Resolving Functions
|
||||
setFeatureTypeParser(parser: FeatureTypeParser<Type, Context>) {
|
||||
this.featureTypeParser = parser.bind({
|
||||
/**
|
||||
* @internal
|
||||
* 内部暴露的非标准 has 方法
|
||||
* 用于实现在返回类型时,查找当前类型是否有注册。
|
||||
* 注意:根据情况,谨慎使用,勿滥用!
|
||||
* Non-standard methods of internal exposure has
|
||||
* Used to find whether the current type is registered when returning a type.
|
||||
* Attention: According to the situation, use it with caution and do not abuse it!
|
||||
*/
|
||||
internalHas: (type: Type | string): boolean => {
|
||||
try {
|
||||
@@ -458,7 +458,7 @@ export class FeatureRegistry<Type, Module, Context = undefined> extends External
|
||||
});
|
||||
}
|
||||
|
||||
// 获取包含对应Tag的Feature模块
|
||||
// Get the Feature Module containing the corresponding Tag
|
||||
getByTag(tag: string): FeatureModule<Type, Module>[] {
|
||||
const { featureMap } = this;
|
||||
const features = [...featureMap.values()].filter(feature =>
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
||||
import type { IExternalStore } from './external-store';
|
||||
|
||||
/**
|
||||
* 订阅拥有 subscribe 和 getSnapshot 方法的抽象 registry 的变化,内部使用 useSyncExternalStore 实现
|
||||
* Changes to the abstract registry with subscribe and getSnapshot methods, internally using useSyncExternalStore implementation
|
||||
*/
|
||||
export const useRegistryState = <T>(registry: IExternalStore<T>) => {
|
||||
const state = useSyncExternalStore(
|
||||
|
||||
@@ -27,13 +27,13 @@ enum ParamsIndex {
|
||||
}
|
||||
|
||||
/**
|
||||
* 与use-data-reporter区分使用
|
||||
* use-data-reporter用于组件场景
|
||||
* data-reporter用于ts/js场景
|
||||
* Distinguished from use-data-reporter
|
||||
* Use-data-reporter for component scenarios
|
||||
* Data-reporter for ts/js scenarios
|
||||
*/
|
||||
class DataReporter {
|
||||
/**
|
||||
* 获取公共的meta信息
|
||||
* Access to public meta information
|
||||
*/
|
||||
getMeta() {
|
||||
const pathName = window.location.pathname;
|
||||
@@ -49,7 +49,7 @@ class DataReporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误事件上报
|
||||
* Error event reporting
|
||||
* @param namespace
|
||||
* @param event
|
||||
*/
|
||||
@@ -62,7 +62,7 @@ class DataReporter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义事件上报
|
||||
* Customs event report
|
||||
* @param namespace
|
||||
* @param event
|
||||
*/
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import { useRef, useEffect } from 'react';
|
||||
|
||||
export function useUnmountSignal() {
|
||||
@@ -35,7 +35,7 @@ export function useUnmountSignal() {
|
||||
return controllerRef.current.signal;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 得是 any
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- must be any
|
||||
export function abortable<T extends (...args: any[]) => any | Promise<any>>(
|
||||
func: T,
|
||||
abortSignal: AbortSignal,
|
||||
|
||||
@@ -35,7 +35,7 @@ const LimitCount: React.FC<LimitCountProps> = ({ maxLen, len }) => (
|
||||
);
|
||||
|
||||
export interface InputWithCountProps extends InputProps {
|
||||
// 设置字数限制并显示字数统计
|
||||
// Set word limits and display word count
|
||||
getValueLength?: (value?: InputProps['value'] | string) => number;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export const getDatabasePageQuery = (): DatabasePageQuery => {
|
||||
};
|
||||
};
|
||||
|
||||
/** 获取 databse 页面模式,如果等于 'modal' 则需要使用全屏形态 */
|
||||
/** Get the Databse page mode, if it is equal to'modal ', you need to use the full-screen mode */
|
||||
export const getDatabasePageMode = (): DatabasePageQuery['page_mode'] => {
|
||||
if (isDatabasePathname()) {
|
||||
return getDatabasePageQuery()?.page_mode;
|
||||
@@ -44,6 +44,6 @@ export const getDatabasePageMode = (): DatabasePageQuery['page_mode'] => {
|
||||
return 'normal';
|
||||
};
|
||||
|
||||
/** 当前 Database 页面模式是弹窗(全屏)形式 */
|
||||
/** The current Database page mode is a pop-up (full screen) format */
|
||||
export const databasePageModeIsModal = (): boolean =>
|
||||
getDatabasePageMode() === 'modal';
|
||||
|
||||
@@ -21,7 +21,7 @@ import { type DocumentSource } from '@coze-arch/bot-api/knowledge';
|
||||
import { isFeishuOrLarkDocumentSource } from './feishu-lark';
|
||||
|
||||
/**
|
||||
* FIXME: 由于后端限制,前端需要在Feishu/Lark路径上去除30天的更新频率,等后续后端解决后即可去掉
|
||||
* FIXME: Due to backend limitations, the front end needs to remove the 30-day update frequency on the Feishu/Lark path, which can be removed after the subsequent backend is resolved.
|
||||
*/
|
||||
export const getUpdateIntervalOptions = (
|
||||
params: {
|
||||
@@ -84,7 +84,7 @@ export const getAppendUpdateIntervalOptions = () => [
|
||||
},
|
||||
];
|
||||
|
||||
// table类型暂时禁用追加更新的更新类型,待后续支持后下掉区分逻辑
|
||||
// Table type Temporarily disables the update type of additional updates, and drops the distinction logic after subsequent support
|
||||
export const getUpdateTypeOptions = (type: FormatType) => [
|
||||
{
|
||||
value: 1,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
const DOMAIN_REGEXP = /^([0-9a-zA-Z-]{1,}\.)+([a-zA-Z]{2,})$/;
|
||||
|
||||
export function isValidUrl(url: string): boolean {
|
||||
@@ -24,7 +24,7 @@ export function isValidUrl(url: string): boolean {
|
||||
// cp-disable-next-line
|
||||
(url.indexOf('https://') !== -1 || url.indexOf('http://') !== -1)
|
||||
);
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch -- 根据函数功能无需 throw error
|
||||
// eslint-disable-next-line @coze-arch/use-error-in-catch -- no need to throw error according to function function
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ const options = {
|
||||
`//lf-cdn.coze.cn/obj/unpkg/pdfjs-dist/${pdfjs.version}/cmaps/`
|
||||
: // cp-disable-next-line
|
||||
`//sf-cdn.coze.com/obj/unpkg-va/pdfjs-dist/${pdfjs.version}/cmaps/`,
|
||||
// 提升性能
|
||||
// Boost performance
|
||||
cMapPacked: true,
|
||||
};
|
||||
|
||||
|
||||
@@ -43,61 +43,61 @@ import styles from './common-file-picker.module.less';
|
||||
|
||||
export interface CommonFilePickerProps
|
||||
extends Omit<TreeProps, 'onChange' | 'onSelect'> {
|
||||
/** 渲染使用的树数据 */
|
||||
/** Tree data used for rendering */
|
||||
treeData: FileNode[];
|
||||
/** 提供一个定制化的 render tree node */
|
||||
/** Provide a customized render tree node */
|
||||
customTreeDataRenderer?: (
|
||||
renderProps: RenderFullLabelProps,
|
||||
) => React.ReactNode;
|
||||
|
||||
/** 是否只能选中叶子结点 如果传入 customRenderTreeData 则失效 */
|
||||
/** Whether only leaf nodes can be selected, invalid if customRenderTreeData is passed in */
|
||||
onlySelectLeaf?: boolean;
|
||||
/** 是否多选 如果自定义 customTreeDataRenderer 一定要传入, 选项会影响 customTreeDataRenderer 的入参 */
|
||||
/** Whether to multi-select, if the custom customTreeDataRenderer must be passed in, the option will affect customTreeDataRenderer imported parameter */
|
||||
multiple?: boolean;
|
||||
|
||||
/**
|
||||
* 是否开启虚拟化
|
||||
* Whether to enable virtualization
|
||||
*/
|
||||
enableVirtualize?: boolean;
|
||||
/** 虚拟化选项 */
|
||||
/** 虚拟化容器高度 */
|
||||
/** virtualization options */
|
||||
/** virtualized container height */
|
||||
virtualizeHeight?: number;
|
||||
/** 每个 item 高度 */
|
||||
/** Height of each item */
|
||||
virtualizeItemSize?: number;
|
||||
|
||||
/** 默认已经选中的内容 可以作为 initValue 使用,也可以作为 value 的代替者使用 */
|
||||
/** The content already selected by default can be used as initValue or as a substitute for value */
|
||||
defaultValue?: FileNode[] | FileId[];
|
||||
|
||||
/** 样式渲染特性 */
|
||||
/** style rendering feature */
|
||||
normalLabelStyle?: React.CSSProperties;
|
||||
selectedLabelStyle?: React.CSSProperties;
|
||||
halfSelectedLabelStyle?: React.CSSProperties;
|
||||
|
||||
/** 缩进大小 默认大小 25px 如果 backgroundMode 为 position 将会反应为 left 如果为 padding 将会反应成 padding-left */
|
||||
/** Indent size, default size 25px If backgroundMode is position, it will react to left If it is padding, it will react to padding-left. */
|
||||
indentSize?: number;
|
||||
|
||||
/** 树组件展开的 icon */
|
||||
/** Tree component expansion icon */
|
||||
expandIcon?: React.ReactNode;
|
||||
|
||||
/** onChange 业务层可以透传 */
|
||||
/** onChange business layer can be passed through */
|
||||
onChange?: (args?: FileNode[]) => void;
|
||||
|
||||
/** onSelect 业务层可以透传 */
|
||||
/** onSelect business layer can pass through */
|
||||
onSelect?: (key: string, selected: boolean, node: FileNode) => void;
|
||||
|
||||
/** 用来转换 selectedFiles, 发生在 设置选中态 到 提交给上层组件 之间 注意 处理有先后顺序,前一个中间件的返回将作为后一个的输入 */
|
||||
/** Used to convert selectedFiles, occurs in, sets the selected state, to submit to the upper component, between, note that there is a sequence of processing, the return of the previous middleware will be used as the input of the latter */
|
||||
transSelectedFilesMiddlewares?: TransSelectedFilesMiddleware[];
|
||||
|
||||
/** 设置选择态的钩子 发生在 点击选中框 到 设置选择态 之间 处理有先后顺序 */
|
||||
/** The hook for setting the selection state occurs between clicking the check box and setting the selection state, and the processing is in sequence */
|
||||
selectFilesMiddlewares?: CalcCurrentSelectFilesMiddleware[];
|
||||
|
||||
/** disable select 禁止选择 */
|
||||
/** Disable select disable select */
|
||||
disableSelect?: boolean;
|
||||
|
||||
/** checkRelation: 父子节点选中态是否关联 */
|
||||
/** checkRelation: Whether the selected state of the parent and child nodes is related */
|
||||
checkRelation?: 'related' | 'unRelated';
|
||||
|
||||
/** 默认展开的节点 key */
|
||||
/** Default expanded node key */
|
||||
defaultExpandKeys?: FileId[];
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ function transDefaultValueToFileNodes(
|
||||
const treeDataMap = levelMapTreeNodesToMap(treeData);
|
||||
return (
|
||||
defaultValue?.map(element => {
|
||||
// 因为开了 onChangeWithObject 所以这里选中态要用 object 存储
|
||||
// Because onChangeWithObject is opened, the selected state here should be stored with object.
|
||||
if (typeof element === 'string') {
|
||||
return (
|
||||
treeDataMap.get(element) ?? {
|
||||
@@ -161,7 +161,7 @@ function transDefaultValueToFileNodes(
|
||||
function transDefaultValueToRenderNode(defaultValue?: FileId[] | FileNode[]) {
|
||||
return (
|
||||
defaultValue?.map(element => {
|
||||
// 因为开了 onChangeWithObject 所以这里选中态要用 object 存储
|
||||
// Because onChangeWithObject is opened, the selected state here should be stored with object.
|
||||
if (typeof element === 'string') {
|
||||
return {
|
||||
key: element,
|
||||
@@ -175,10 +175,10 @@ function transDefaultValueToRenderNode(defaultValue?: FileId[] | FileNode[]) {
|
||||
/**
|
||||
* ------------------
|
||||
* common file picker
|
||||
* 用于数据上传选择文件
|
||||
* Select file for data upload
|
||||
* ------------------
|
||||
* useImperativeHandle:
|
||||
* search: 提供树搜索能力
|
||||
* Search: Provides tree search capability
|
||||
* ------------------
|
||||
* props: FilePickerProps
|
||||
* ------------------
|
||||
@@ -205,7 +205,7 @@ export const CommonFilePicker = React.forwardRef(
|
||||
} = props;
|
||||
|
||||
const treeRef = useRef<Tree>(null);
|
||||
// 使用受控模式
|
||||
// Use controlled mode
|
||||
const [selectValue, setSelectValue] = useState<FileNode[]>(
|
||||
transDefaultValueToRenderNode(defaultValue),
|
||||
);
|
||||
@@ -287,13 +287,13 @@ export const CommonFilePicker = React.forwardRef(
|
||||
transedChangeNodes = [changeNodes as unknown as FileNode];
|
||||
}
|
||||
|
||||
// 计算 diff
|
||||
// Calculate diff
|
||||
const [addNodes, removeNodes, retainNodes] = diffChangeNodes(
|
||||
prevChangeNodes.current,
|
||||
transedChangeNodes as FileNode[],
|
||||
);
|
||||
|
||||
// 这里的中间件更多用在定制化选中态的场景 比如想要反选所有子节点但是保持父亲节点选中
|
||||
// The middleware here is more used in scenarios where the selected state is customized, such as wanting to unselect all sub-nodes but keep the parent node selected
|
||||
transedChangeNodes = distinctFileNodes(
|
||||
selectFilesMiddlewares.reduce(
|
||||
(selectedElements, middleware) =>
|
||||
@@ -313,10 +313,10 @@ export const CommonFilePicker = React.forwardRef(
|
||||
key: transedNode.key,
|
||||
})),
|
||||
);
|
||||
// 虽然这里返回的是父节点带 children 但是因为都是后端一次接口的快照
|
||||
// 具体上报什么数据交给业务方
|
||||
// 这里的中间件主要用在定制化上报给上层组件的选中数据的场景,比如 checkRelation 'related' 模式下 上面返回的只有父亲节点的数据,但是父组件想要所有数据
|
||||
// 警告:这里如果使用 loadData 时拿不到没请求的子节点(换句话说拿到的最后一层的数据不一定是叶子结点, 同样的在这里选中之后交回给后端的其实也只是一个父节点 不能保证在一个快照里
|
||||
// Although the parent node with children is returned here, because they are all snapshots of the backend interface
|
||||
// What data to report to the business party?
|
||||
// The middleware here is mainly used in scenarios where the selected data is reported to the upper component, such as checkRelation'related 'mode, where only the data of the parent node is returned, but the parent component wants all the data
|
||||
// Warning: If you can't get the unrequested sub-node when using loadData here (in other words, the last layer of data you get is not necessarily a leaf node), the same is true here. After selecting it, it is only a parent node, and it cannot be guaranteed to be in a snapshot.
|
||||
if (transSelectedFilesMiddlewares) {
|
||||
transedChangeNodes = transSelectedFilesMiddlewares.reduce(
|
||||
(selectedElements, middleware) => middleware(selectedElements),
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// 缩进相关数据
|
||||
// Indent related data
|
||||
export const DEFAULT_FILENODE_LEVEL_INDENT = 24;
|
||||
|
||||
// 虚拟化配置相关数据
|
||||
// Virtualization configuration related data
|
||||
export const DEFAULT_VIRTUAL_CONTAINER_HEIGHT = 540;
|
||||
export const DEFAULT_VIRTUAL_ITEM_HEIGHT = 28;
|
||||
|
||||
@@ -80,7 +80,7 @@ const ActionComponent = (props: {
|
||||
<span className="action-placeholder file-selector"></span>
|
||||
);
|
||||
|
||||
// 当前整棵树是多选 并且 只能选中叶子结点
|
||||
// At present, the entire tree is multi-selected, and only leaf nodes can be selected
|
||||
if (multiple && onlySelectLeaf) {
|
||||
return isLeaf && !unCheckable ? (
|
||||
<>
|
||||
@@ -94,7 +94,7 @@ const ActionComponent = (props: {
|
||||
);
|
||||
}
|
||||
|
||||
// 当前整棵树是多选 并且 能选中所有结点
|
||||
// The current entire tree is multi-selected, and all nodes can be selected
|
||||
if (multiple && !onlySelectLeaf) {
|
||||
if (unCheckable) {
|
||||
return (
|
||||
@@ -111,7 +111,7 @@ const ActionComponent = (props: {
|
||||
);
|
||||
}
|
||||
|
||||
// 当前整棵树是单选 并且 只能选中叶子结点
|
||||
// The current entire tree is radio-selected, and only leaf nodes can be selected
|
||||
if (!multiple && onlySelectLeaf) {
|
||||
return isLeaf && !unCheckable ? (
|
||||
<>
|
||||
@@ -126,7 +126,7 @@ const ActionComponent = (props: {
|
||||
);
|
||||
}
|
||||
|
||||
// 当前整棵树是单选 并且 能选中所有结点
|
||||
// The current entire tree is radio-selected, and all nodes can be selected
|
||||
if (!multiple && !onlySelectLeaf) {
|
||||
if (unCheckable) {
|
||||
return (
|
||||
@@ -195,12 +195,12 @@ const LabelContent = (props: {
|
||||
|
||||
/**
|
||||
* -----------------------------
|
||||
* 获取默认的文件树 label renderer 这层不感知平台信息
|
||||
* Get the default file tree label renderer layer that is not platform aware
|
||||
* -----------------------------
|
||||
* @param {boolean} multiple 是否多选
|
||||
* @param {boolean} onlySelectLeaf 是否只能选中叶子结点
|
||||
* @param {{indentSize: 树组件缩进尺寸, expandIcon: 树组件可展开节点 展开图标, disableSelect: 选择的 disable 状态}} renderOption 渲染相关的自定义选项
|
||||
* @returns label render 函数
|
||||
* @param {boolean} multiple or not
|
||||
* @param {boolean} onlySelectLeaf Whether only leaf nodes can be selected
|
||||
* @param {{indentSize: tree component indent size, expandIcon: tree component expandable node, expand icon, disableSelect: selected disabled state}} renderOption Render-related customization options
|
||||
* @Returns label rendering function
|
||||
*/
|
||||
export function useDefaultLabelRenderer(
|
||||
multiple: boolean,
|
||||
@@ -243,10 +243,10 @@ export function useDefaultLabelRenderer(
|
||||
}`;
|
||||
|
||||
/**
|
||||
* 在整行点击的处理函数 不是点击 checkbox 或者 radio 或者 expandIcon 的处理函数
|
||||
* @param isLeaf: 是否是叶子结点
|
||||
* @param onExpand: 展开行 处理函数
|
||||
* @param onCheck: 选中状态的 toggle
|
||||
* The handler that clicks on the entire line, not the handler that clicks on checkbox or radio or expandIcon
|
||||
* @Param isLeaf: whether it is a leaf node
|
||||
* @Param onExpand: expand the line, handle function
|
||||
* @param onCheck: toggle in selected state
|
||||
**/
|
||||
const getItemAction = (params: {
|
||||
isLeaf: boolean;
|
||||
@@ -256,8 +256,8 @@ export function useDefaultLabelRenderer(
|
||||
}) => {
|
||||
const { onExpand, onCheck, isLeaf, unCheckable } = params;
|
||||
return function (e: React.MouseEvent<Element, MouseEvent>) {
|
||||
// 如果只能选中叶子结点 那么无论 多选 / 单选 父节点只能展开,叶子结点可以选中
|
||||
// 反之 父节点子节点 都是选中 无论多选单选,想要展开就点击 expand icon
|
||||
// If only the leaf node can be selected, then no matter whether it is multi-select/single-select, the parent node can only be expanded, and the leaf node can be selected
|
||||
// On the contrary, the parent node sub-node is selected, regardless of the multi-select radio, if you want to expand, click the expand icon.
|
||||
if (onlySelectLeaf) {
|
||||
if (!isLeaf) {
|
||||
onExpand(e);
|
||||
@@ -301,14 +301,14 @@ export function useDefaultLabelRenderer(
|
||||
readonly,
|
||||
unCheckable = false,
|
||||
} = data;
|
||||
// 单选选中选项的 key
|
||||
// Radio select the key of the selected option
|
||||
const singleSelectCheckStatus = singleSelectedKey === key;
|
||||
const rowCheckStatus = multiple
|
||||
? checkStatus
|
||||
: {
|
||||
checked: singleSelectCheckStatus,
|
||||
};
|
||||
// 只要 data isLeaf 不是空 永远先看 data.isLeaf
|
||||
// As long as data isLeaf is not empty, always look at data.isLeaf first
|
||||
const isLeaf = data?.isLeaf ?? !(data.children && data.children.length);
|
||||
const checkItem = multiple
|
||||
? (e: React.MouseEvent<Element, MouseEvent>) => {
|
||||
|
||||
@@ -30,53 +30,53 @@ export interface PickerRef {
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件选择树节点
|
||||
* file selection tree node
|
||||
*/
|
||||
export interface FileNode extends TreeNodeData {
|
||||
/** 独一无二的 key 标识 可以用 文件 id */
|
||||
/** Unique key identifier, can be used, file id */
|
||||
key: string;
|
||||
value?: string;
|
||||
label?: React.ReactNode;
|
||||
type?: TreeNodeType;
|
||||
// icon 的 URL
|
||||
// URL of the icon
|
||||
icon?: string;
|
||||
children?: FileNode[];
|
||||
/** 标识当前节点是不是叶子结点 loadData 时必备 */
|
||||
/** Required when identifying whether the current node is a leaf node loadData */
|
||||
isLeaf?: boolean;
|
||||
/** 该节点是否可以选中 */
|
||||
/** Can the node be selected? */
|
||||
selectable?: boolean;
|
||||
/** 节点的 loading 状态,开启后 loading 默认替换 icon,展示 loadingInfo,
|
||||
* 注意这个和 semi 本身带的 loading 不一样,semi 的 loading 指的是 展开的 loading 状态 */
|
||||
/** The loading status of the node, after opening, the loading default replaces the icon, and displays the loadingInfo.
|
||||
* Note that this is different from the loading of the semi itself. The loading of the semi refers to the unfolded loading state. */
|
||||
isLoading?: boolean;
|
||||
/** 节点 loading 的提示,默认是 `获取中` */
|
||||
/** Node loading prompt, the default is "getting" */
|
||||
loadingInfo?: string;
|
||||
/** 具体的文档类型 比如 doc docx txt 等 */
|
||||
/** Specific document types, such as doc docx txt, etc */
|
||||
file_type?: string;
|
||||
/** 三方文档链接 */
|
||||
/** Three-way document link */
|
||||
file_url?: string;
|
||||
/** 【飞书场景】wiki 空间id,*/
|
||||
/** [Feishu scene] wiki space id,*/
|
||||
space_id?: string;
|
||||
/** 【飞书场景】wiki 叶子id,*/
|
||||
/** [Feishu scene] wiki leaf id,*/
|
||||
obj_token?: string;
|
||||
/** 自定义渲染 Item */
|
||||
/** Custom Rendering Items */
|
||||
render?: () => ReactNode;
|
||||
/** 只读,不可交互 */
|
||||
/** Read-only, not interactive */
|
||||
readonly?: boolean;
|
||||
/** 节点是否不可选择,默认为 false */
|
||||
/** Whether the node is not selectable, the default is false */
|
||||
unCheckable?: boolean;
|
||||
}
|
||||
|
||||
export type FileId = string;
|
||||
|
||||
/**
|
||||
* 文件选择树 节点选择状态
|
||||
* File selection tree, node selection status
|
||||
*/
|
||||
export interface FileSelectCheckStatus {
|
||||
checked: boolean;
|
||||
halfChecked: boolean;
|
||||
}
|
||||
|
||||
// 三部分 当前选中的 新增选中的 较上次不选中的 较上次不变的
|
||||
// Three parts, currently selected, newly selected, unselected from last time, unchanged from last time
|
||||
export type TransSelectedFilesMiddleware = (
|
||||
fileNodes: FileNode[],
|
||||
) => FileNode[];
|
||||
@@ -88,12 +88,12 @@ export type CalcCurrentSelectFilesMiddleware = (
|
||||
retainNodes?: FileNode[],
|
||||
) => FileNode[];
|
||||
|
||||
/** 类型断言 节点是不是 fileNode */
|
||||
/** Type assertion, node is not fileNode */
|
||||
export function isFileNode(fileNode: unknown): fileNode is FileNode {
|
||||
return !!fileNode && isObject(fileNode) && !!(fileNode as FileNode).key;
|
||||
}
|
||||
|
||||
/** 类型断言 数组是不是 fileNode 数组 */
|
||||
/** Type assertion, array is not a fileNode array */
|
||||
export function isFileNodeArray(fileNodes: unknown[]): fileNodes is FileNode[] {
|
||||
return fileNodes.every(fileNode => isFileNode(fileNode));
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ const SegmentMenu: React.FC<ISegmentMenuProps> = props => {
|
||||
) : null}
|
||||
<div className="pl-2 h-6 mt-4 mb-1 flex items-center">
|
||||
<div className="coz-fg-secondary text-[12px] font-[400] leading-4 shrink-0">
|
||||
{/**文档列表 */}
|
||||
{/**document list */}
|
||||
{I18n.t('knowledge_level_012')}
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,7 +114,7 @@ const SegmentMenu: React.FC<ISegmentMenuProps> = props => {
|
||||
<div className="flex flex-col gap-1 !overflow-auto">
|
||||
<div className="w-full pl-2 h-6 items-center flex gap-[4px]">
|
||||
<div className="coz-fg-secondary text-[12px] font-[400] leading-4 shrink-0">
|
||||
{/**分段层级 */}
|
||||
{/**segment hierarchy */}
|
||||
{I18n.t('knowledge_level_adjust')}
|
||||
</div>
|
||||
{treeDisabled ? null : (
|
||||
|
||||
@@ -48,10 +48,10 @@ export const SegmentTree: React.FC<ISegmentTreeProps> = ({
|
||||
disabled,
|
||||
}) => {
|
||||
/**
|
||||
* 选中功能
|
||||
* select function
|
||||
*/
|
||||
const [selected, setSelected] = useState(new Set<string>());
|
||||
// 分片 id
|
||||
// Sharding id
|
||||
const [selectedThroughParent, setSelectedThroughParent] = useState(
|
||||
new Set<string>(),
|
||||
);
|
||||
|
||||
@@ -114,8 +114,8 @@ export function useSegmentContextMenu({
|
||||
onContextMenu: (e, node: NodeApi<LevelDocumentTree>) => {
|
||||
e.preventDefault();
|
||||
setTreeNode(node);
|
||||
/** 在 project ide 里面,ide 容器设置了 contain: strict, 会导致 fixed position
|
||||
* 的偏移基础不对,所以这里需要减去 ide 容器的 left 和 top 值
|
||||
/** In the project ide, the ide container is set to contain: strict, which will cause a fixed position.
|
||||
* The base of the offset is wrong, so you need to subtract the left and top values of the ide container here
|
||||
*/
|
||||
let clickX = e.pageX;
|
||||
let clickY = e.pageY;
|
||||
|
||||
@@ -140,7 +140,7 @@ export const handleTreeNodeMove = (
|
||||
};
|
||||
}
|
||||
|
||||
// 如果是同一个 parent,且拖动的位置在当前位置之前,dropIndex 减 1
|
||||
// If it is the same parent and the dragged position is before the current position, the dropIndex is reduced by 1.
|
||||
const originalIndex = parentSegment.children.indexOf(dragSegment.id);
|
||||
const dropIndex =
|
||||
originalIndex < positions.dropIndex &&
|
||||
@@ -224,7 +224,7 @@ export const handleMergeNodes = (
|
||||
};
|
||||
}
|
||||
|
||||
// 从原父节点的 children 中移除该节点
|
||||
// Remove the node from the children of the original parent
|
||||
const parentSegment = resSegments.find(
|
||||
item => item.id === segmentToMerge.parent,
|
||||
);
|
||||
@@ -235,7 +235,7 @@ export const handleMergeNodes = (
|
||||
}
|
||||
|
||||
if (!['table', 'image', 'title'].includes(segmentToMerge?.type ?? '')) {
|
||||
// 合并文本内容并删除节点
|
||||
// Merge text content and delete nodes
|
||||
mergedSegment.text += segmentToMerge.text;
|
||||
const index = resSegments.findIndex(
|
||||
item => item.id === segmentToMerge.id,
|
||||
@@ -244,7 +244,7 @@ export const handleMergeNodes = (
|
||||
resSegments.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
// 非section-text类型的节点,将其移动到合并后节点的children中
|
||||
// Nodes of type non-section-text, move them to the children of the merged node
|
||||
segmentToMerge.parent = mergedSegment.id;
|
||||
mergedSegment.children.push(segmentToMerge.id);
|
||||
}
|
||||
|
||||
@@ -33,33 +33,33 @@ export const DocumentChunkPreview = ({
|
||||
<div
|
||||
id={locateId}
|
||||
className={classNames(
|
||||
// 布局
|
||||
// layout
|
||||
'relative',
|
||||
// 间距
|
||||
// spacing
|
||||
'mb-2 p-2',
|
||||
// 文字样式
|
||||
// Text Style
|
||||
'text-sm leading-5',
|
||||
// 颜色
|
||||
// color
|
||||
'coz-fg-primary hover:coz-mg-hglt-secondary-hovered coz-mg-secondary',
|
||||
// 边框
|
||||
// border
|
||||
'border border-solid coz-stroke-primary rounded-lg',
|
||||
// 表格样式
|
||||
// table style
|
||||
getEditorTableClassname(),
|
||||
// 图片样式
|
||||
// image style
|
||||
getEditorImgClassname(),
|
||||
// 换行
|
||||
// line feed
|
||||
getEditorWordsCls(),
|
||||
)}
|
||||
>
|
||||
<p
|
||||
// 已使用 DOMPurify 过滤 xss
|
||||
// Filtered xss with DOMPurify
|
||||
// eslint-disable-next-line risxss/catch-potential-xss-react
|
||||
dangerouslySetInnerHTML={{
|
||||
__html:
|
||||
DOMPurify.sanitize(getRenderHtmlContent(chunk.content ?? ''), {
|
||||
/**
|
||||
* 1. 防止CSS注入攻击
|
||||
* 2. 防止用户误写入style标签,导致全局样式被修改,页面展示异常
|
||||
* 1. Prevent CSS injection attacks
|
||||
* 2. Prevent users from writing the style tag by mistake, resulting in the global style being modified and the page display being abnormal
|
||||
*/
|
||||
FORBID_TAGS: ['style'],
|
||||
}) ?? '',
|
||||
|
||||
@@ -21,16 +21,16 @@ import type { Emitter, Handler, EventType } from 'mitt';
|
||||
|
||||
import { type Chunk } from '../types/chunk';
|
||||
|
||||
// 定义事件名称字面量类型
|
||||
// Define event name literal type
|
||||
export type EventTypeName =
|
||||
| 'previewContextMenuItemAction'
|
||||
| 'hoverEditBarAction';
|
||||
|
||||
/**
|
||||
* 事件类型定义
|
||||
* event type definition
|
||||
*/
|
||||
export interface EventTypes extends Record<EventType, unknown> {
|
||||
// 右键菜单相关事件
|
||||
// right-click menu related events
|
||||
previewContextMenuItemAction: {
|
||||
type: 'add-after' | 'add-before' | 'delete' | 'edit';
|
||||
targetChunk: Chunk;
|
||||
@@ -38,7 +38,7 @@ export interface EventTypes extends Record<EventType, unknown> {
|
||||
chunks?: Chunk[];
|
||||
};
|
||||
|
||||
// 悬浮编辑栏相关事件
|
||||
// Floating edit bar related events
|
||||
hoverEditBarAction: {
|
||||
type: 'add-after' | 'add-before' | 'delete' | 'edit';
|
||||
targetChunk: Chunk;
|
||||
@@ -48,32 +48,32 @@ export interface EventTypes extends Record<EventType, unknown> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件处理函数类型
|
||||
* event handler type
|
||||
*/
|
||||
export type EventHandler<T extends EventTypeName> = Handler<EventTypes[T]>;
|
||||
|
||||
/**
|
||||
* 创建事件总线实例
|
||||
* Create an event bus instance
|
||||
*/
|
||||
export const createEventBus = (): Emitter<EventTypes> => mitt<EventTypes>();
|
||||
|
||||
/**
|
||||
* 全局事件总线实例
|
||||
* Global Event Bus instance
|
||||
*/
|
||||
export const eventBus = createEventBus();
|
||||
|
||||
/**
|
||||
* 事件总线钩子
|
||||
* 用于在组件中使用事件总线
|
||||
* event bus hook
|
||||
* Used to use the event bus in components
|
||||
*/
|
||||
export const useEventBus = () => eventBus;
|
||||
|
||||
/**
|
||||
* 监听事件钩子
|
||||
* 用于在组件中监听事件
|
||||
* @param eventName 事件名称
|
||||
* @param handler 事件处理函数
|
||||
* @param deps 依赖数组,当依赖变化时重新绑定事件
|
||||
* listen event hook
|
||||
* Used to listen for events in a component
|
||||
* @param eventName
|
||||
* @param handler event handler
|
||||
* @Param deps dependency arrays, rebind events when dependencies change
|
||||
*/
|
||||
export const useEventListener = <T extends EventTypeName>(
|
||||
eventName: T,
|
||||
@@ -81,10 +81,10 @@ export const useEventListener = <T extends EventTypeName>(
|
||||
deps: React.DependencyList = [],
|
||||
) => {
|
||||
useEffect(() => {
|
||||
// 绑定事件
|
||||
// binding event
|
||||
eventBus.on(eventName, handler as Handler<unknown>);
|
||||
|
||||
// 组件卸载时解绑事件
|
||||
// Unbind event when component is unmounted
|
||||
return () => {
|
||||
eventBus.off(eventName, handler as Handler<unknown>);
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ export const BaseUploadImage = ({
|
||||
showTooltip,
|
||||
renderUI,
|
||||
}: BaseUploadImageProps) => {
|
||||
// 处理图片上传
|
||||
// Handle image upload
|
||||
const handleImageUpload = (object: customRequestArgs) => {
|
||||
if (!editor) {
|
||||
return;
|
||||
@@ -53,7 +53,7 @@ export const BaseUploadImage = ({
|
||||
options: {
|
||||
onFinish: (result: { url?: string; tosKey?: string }) => {
|
||||
if (result.url && editor) {
|
||||
// 插入图片到编辑器
|
||||
// Insert pictures into the editor
|
||||
editor.chain().focus().setImage({ src: result.url }).run();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -79,7 +79,7 @@ export const handleCustomUploadRequest = async ({
|
||||
}
|
||||
|
||||
try {
|
||||
// 业务逻辑
|
||||
// business logic
|
||||
onBeforeUpload?.();
|
||||
const { name, fileInstance } = file;
|
||||
|
||||
|
||||
@@ -49,14 +49,14 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = props => {
|
||||
const contextMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
/**
|
||||
* 当右键点击编辑器时,显示上下文菜单
|
||||
* When right-clicking on the editor, the context menu is displayed
|
||||
*/
|
||||
const { contextMenuPosition, openContextMenu } = useControlContextMenu({
|
||||
contextMenuRef,
|
||||
});
|
||||
|
||||
/**
|
||||
* 当点击编辑器外部时
|
||||
* When clicking outside the editor
|
||||
*/
|
||||
useOutEditorMode({
|
||||
editorRef,
|
||||
@@ -76,26 +76,26 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = props => {
|
||||
<div
|
||||
ref={editorRef}
|
||||
className={classNames(
|
||||
// 布局
|
||||
// layout
|
||||
'relative',
|
||||
// 间距
|
||||
// spacing
|
||||
'mb-2 p-2',
|
||||
// 文字样式
|
||||
// Text Style
|
||||
'text-sm leading-5',
|
||||
// 颜色
|
||||
// color
|
||||
'coz-fg-primary coz-bg-max',
|
||||
// 边框
|
||||
// border
|
||||
'border border-solid coz-stroke-hglt rounded-lg',
|
||||
)}
|
||||
onContextMenu={openContextMenu}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
// 表格样式
|
||||
// table style
|
||||
getEditorTableClassname(),
|
||||
// 图片样式
|
||||
// image style
|
||||
getEditorImgClassname(),
|
||||
// 换行
|
||||
// line feed
|
||||
getEditorWordsCls(),
|
||||
)}
|
||||
>
|
||||
@@ -104,7 +104,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = props => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右键菜单 */}
|
||||
{/* right-click menu */}
|
||||
{contextMenuPosition && editorContextMenuItemsRegistry ? (
|
||||
<EditorContextMenu
|
||||
x={contextMenuPosition.x}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const AddAfterAction: React.FC<HoverEditBarActionProps> = ({
|
||||
chunks,
|
||||
disabled,
|
||||
}) => {
|
||||
// 在特定分片后添加新分片
|
||||
// Add new shardings after specific shardings
|
||||
const { addEmptyChunkAfter } = useAddEmptyChunkAction({
|
||||
chunks: chunks || [],
|
||||
onChunksChange: ({ newChunk, chunks: newChunks }) => {
|
||||
|
||||
@@ -27,14 +27,14 @@ import { eventBus } from '@/text-knowledge-editor/event';
|
||||
import { type HoverEditBarActionProps } from './module';
|
||||
|
||||
/**
|
||||
* 在特定分片前添加新分片的操作组件
|
||||
* Add the action component of a new sharding before a specific sharding
|
||||
*/
|
||||
export const AddBeforeAction: React.FC<HoverEditBarActionProps> = ({
|
||||
chunk,
|
||||
chunks = [],
|
||||
disabled,
|
||||
}) => {
|
||||
// 在特定分片前添加新分片
|
||||
// Add new shardings before specific shardings
|
||||
const { addEmptyChunkBefore } = useAddEmptyChunkAction({
|
||||
chunks,
|
||||
onChunksChange: ({ newChunk, chunks: newChunks }) => {
|
||||
|
||||
@@ -26,19 +26,19 @@ import { eventBus } from '@/text-knowledge-editor/event';
|
||||
import { type HoverEditBarActionProps } from './module';
|
||||
|
||||
/**
|
||||
* 删除特定分片的操作组件
|
||||
* Remove the action component for specific shardings
|
||||
*
|
||||
* 内部实现了删除特定分片的逻辑
|
||||
* 如果传入了 onDelete 回调,则会在点击时调用
|
||||
* 如果提供了 chunks、onChunksChange,则会在内部处理删除逻辑,
|
||||
* 无需依赖外部的 usePreviewContextMenu
|
||||
* The logic to remove specific shardings is implemented internally
|
||||
* If an onDelete callback is passed, it will be called on click
|
||||
* If chunks, onChunksChange are provided, the deletion logic is handled internally.
|
||||
* No need to rely on external usePreviewContextMenu
|
||||
*/
|
||||
export const DeleteAction: React.FC<HoverEditBarActionProps> = ({
|
||||
chunk,
|
||||
chunks = [],
|
||||
disabled,
|
||||
}) => {
|
||||
// 删除特定分片
|
||||
// Remove specific shardings
|
||||
const { deleteChunk } = useDeleteAction({
|
||||
chunks,
|
||||
onChunksChange: ({ chunks: newChunks }) => {
|
||||
|
||||
@@ -26,10 +26,10 @@ import { eventBus } from '@/text-knowledge-editor/event';
|
||||
import { type HoverEditBarActionProps } from './module';
|
||||
|
||||
/**
|
||||
* 编辑操作组件
|
||||
* Edit action component
|
||||
*
|
||||
* 内部实现了激活特定分片的编辑模式的逻辑
|
||||
* 如果传入了 onEdit 回调,则会在点击时调用
|
||||
* The logic to activate the edit mode for specific shardings is implemented internally
|
||||
* If an onEdit callback is passed, it will be called on click
|
||||
*/
|
||||
export const EditAction: React.FC<HoverEditBarActionProps> = ({
|
||||
chunk,
|
||||
|
||||
@@ -27,7 +27,7 @@ import { eventBus } from '@/text-knowledge-editor/event';
|
||||
import { type PreviewContextMenuItemProps } from './module';
|
||||
|
||||
/**
|
||||
* 在特定分片后添加新分片的菜单项组件
|
||||
* Add a new sharding's menu item component after a specific sharding
|
||||
*/
|
||||
export const AddAfterAction: React.FC<PreviewContextMenuItemProps> = ({
|
||||
chunk,
|
||||
@@ -44,11 +44,11 @@ export const AddAfterAction: React.FC<PreviewContextMenuItemProps> = ({
|
||||
'cursor-not-allowed': isDisabled,
|
||||
});
|
||||
|
||||
// 在特定分片后添加新分片
|
||||
// Add new shardings after specific shardings
|
||||
const { addEmptyChunkAfter } = useAddEmptyChunkAction({
|
||||
chunks,
|
||||
onChunksChange: ({ newChunk, chunks: newChunks }) => {
|
||||
// 发出在特定分片后添加新分片的事件
|
||||
// Issue an event to add a new sharding after a specific sharding
|
||||
eventBus.emit('previewContextMenuItemAction', {
|
||||
type: 'add-after',
|
||||
newChunk,
|
||||
|
||||
@@ -27,7 +27,7 @@ import { eventBus } from '@/text-knowledge-editor/event';
|
||||
import { type PreviewContextMenuItemProps } from './module';
|
||||
|
||||
/**
|
||||
* 在特定分片前添加新分片的菜单项组件
|
||||
* Add a new sharding's menu item component before a specific sharding
|
||||
*/
|
||||
export const AddBeforeAction: React.FC<PreviewContextMenuItemProps> = ({
|
||||
chunk,
|
||||
@@ -44,7 +44,7 @@ export const AddBeforeAction: React.FC<PreviewContextMenuItemProps> = ({
|
||||
'cursor-not-allowed': isDisabled,
|
||||
});
|
||||
|
||||
// 在特定分片前添加新分片
|
||||
// Add new shardings before specific shardings
|
||||
const { addEmptyChunkBefore } = useAddEmptyChunkAction({
|
||||
chunks,
|
||||
onChunksChange: ({ newChunk, chunks: newChunks }) => {
|
||||
|
||||
@@ -27,7 +27,7 @@ import { eventBus } from '@/text-knowledge-editor/event';
|
||||
import { type PreviewContextMenuItemProps } from './module';
|
||||
|
||||
/**
|
||||
* 删除特定分片的菜单项组件
|
||||
* Remove a specific sharding menu item component
|
||||
*/
|
||||
export const DeleteAction: React.FC<PreviewContextMenuItemProps> = ({
|
||||
chunk,
|
||||
@@ -44,7 +44,7 @@ export const DeleteAction: React.FC<PreviewContextMenuItemProps> = ({
|
||||
'cursor-not-allowed': isDisabled,
|
||||
});
|
||||
|
||||
// 删除特定分片
|
||||
// Remove specific shardings
|
||||
const { deleteChunk } = useDeleteAction({
|
||||
chunks,
|
||||
onChunksChange: ({ chunks: newChunks }) => {
|
||||
|
||||
@@ -26,10 +26,10 @@ import { eventBus } from '@/text-knowledge-editor/event';
|
||||
import { type PreviewContextMenuItemProps } from './module';
|
||||
|
||||
/**
|
||||
* 编辑操作菜单项组件
|
||||
* Edit Action Menu Item Component
|
||||
*
|
||||
* 内部实现了激活特定分片的编辑模式的逻辑
|
||||
* 如果传入了 onEdit 回调,则会在点击时调用
|
||||
* The logic to activate the edit mode for specific shardings is implemented internally
|
||||
* If an onEdit callback is passed, it will be called on click
|
||||
*/
|
||||
export const EditAction: React.FC<PreviewContextMenuItemProps> = ({
|
||||
chunk,
|
||||
|
||||
@@ -58,7 +58,7 @@ const DocumentPreviewComponent: React.FC<DocumentPreviewProps> = props => {
|
||||
<div className="relative">
|
||||
<div
|
||||
className={classNames(
|
||||
// 布局
|
||||
// layout
|
||||
'relative overflow-hidden',
|
||||
)}
|
||||
onContextMenu={readonly ? undefined : e => openContextMenu(e)}
|
||||
@@ -70,7 +70,7 @@ const DocumentPreviewComponent: React.FC<DocumentPreviewProps> = props => {
|
||||
onMouseLeave={readonly ? undefined : handleMouseLeave}
|
||||
onDoubleClick={readonly ? undefined : () => onActivateEditMode?.(chunk)}
|
||||
>
|
||||
{/* 悬停时显示的操作栏 */}
|
||||
{/* The action bar displayed when hovering */}
|
||||
{hoveredChunk === chunk.text_knowledge_editor_chunk_uuid &&
|
||||
!readonly ? (
|
||||
<HoverEditBar
|
||||
@@ -83,7 +83,7 @@ const DocumentPreviewComponent: React.FC<DocumentPreviewProps> = props => {
|
||||
<DocumentChunkPreview chunk={chunk} locateId={locateId || ''} />
|
||||
</div>
|
||||
|
||||
{/* 右键菜单 */}
|
||||
{/* right-click menu */}
|
||||
{contextMenuPosition ? (
|
||||
<PreviewContextMenu
|
||||
previewContextMenuItemsRegistry={previewContextMenuItemsRegistry}
|
||||
@@ -99,16 +99,16 @@ const DocumentPreviewComponent: React.FC<DocumentPreviewProps> = props => {
|
||||
);
|
||||
};
|
||||
|
||||
// 使用React.memo包装组件,避免不必要的重新渲染
|
||||
// Wrap components with React.memo to avoid unnecessary re-rendering
|
||||
export const DocumentPreview = React.memo(
|
||||
DocumentPreviewComponent,
|
||||
(prevProps, nextProps) => {
|
||||
// 如果分片内容变化,需要重新渲染
|
||||
// If the sharding content changes, it needs to be re-rendered
|
||||
if (prevProps.chunk.content !== nextProps.chunk.content) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 其他情况下不需要重新渲染
|
||||
// In other cases, no re-rendering is required
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -28,11 +28,11 @@ export const useControlContextMenu = ({
|
||||
y: number;
|
||||
} | null>(null);
|
||||
|
||||
// 处理右键菜单
|
||||
// Handle right-click menus
|
||||
const openContextMenu = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// 计算相对于事件目标元素的位置
|
||||
// Calculate the position relative to the event target element
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const relativeX = e.clientX - rect.left;
|
||||
const relativeY = e.clientY - rect.top;
|
||||
@@ -43,15 +43,15 @@ export const useControlContextMenu = ({
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭右键菜单
|
||||
// Close the right-click menu
|
||||
const closeContextMenu = () => {
|
||||
setContextMenuPosition(null);
|
||||
};
|
||||
|
||||
// 处理点击文档其他位置
|
||||
// Process Click Document Other Locations
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
// 如果点击的是右键菜单外部,则关闭菜单
|
||||
// If you click outside the right-click menu, close the menu
|
||||
if (
|
||||
contextMenuRef.current &&
|
||||
!contextMenuRef.current.contains(event.target as Node)
|
||||
|
||||
@@ -28,7 +28,7 @@ export const useControlEditorContextMenu = ({
|
||||
y: number;
|
||||
} | null>(null);
|
||||
|
||||
// 处理右键菜单
|
||||
// Handle right-click menus
|
||||
const openContextMenu = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setContextMenuPosition({
|
||||
@@ -37,15 +37,15 @@ export const useControlEditorContextMenu = ({
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭右键菜单
|
||||
// Close the right-click menu
|
||||
const closeContextMenu = () => {
|
||||
setContextMenuPosition(null);
|
||||
};
|
||||
|
||||
// 处理点击文档其他位置
|
||||
// Process Click Document Other Locations
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
// 如果点击的是右键菜单外部,则关闭菜单
|
||||
// If you click outside the right-click menu, close the menu
|
||||
if (
|
||||
contextMenuRef.current &&
|
||||
!contextMenuRef.current.contains(event.target as Node)
|
||||
|
||||
@@ -26,7 +26,7 @@ export const useControlPreviewContextMenu = () => {
|
||||
} | null>(null);
|
||||
const contextMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// 处理右键点击事件
|
||||
// Handling right-click events
|
||||
const openContextMenu = (e: React.MouseEvent, chunk: Chunk) => {
|
||||
e.preventDefault();
|
||||
setContextMenuInfo({
|
||||
@@ -36,12 +36,12 @@ export const useControlPreviewContextMenu = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭右键菜单
|
||||
// Close the right-click menu
|
||||
const closeContextMenu = () => {
|
||||
setContextMenuInfo(null);
|
||||
};
|
||||
|
||||
// 点击文档其他位置关闭右键菜单
|
||||
// Click elsewhere in the document to close the right-click menu
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
|
||||
@@ -19,12 +19,12 @@ import { useState } from 'react';
|
||||
export const useHoverEffect = () => {
|
||||
const [hoveredChunk, setHoveredChunk] = useState<string | null>(null);
|
||||
|
||||
// 处理鼠标悬停事件
|
||||
// Handling mouse hover events
|
||||
const handleMouseEnter = (chunkId: string) => {
|
||||
setHoveredChunk(chunkId);
|
||||
};
|
||||
|
||||
// 处理鼠标离开事件
|
||||
// Handling mouse away events
|
||||
const handleMouseLeave = () => {
|
||||
setHoveredChunk(null);
|
||||
};
|
||||
|
||||
@@ -27,10 +27,10 @@ export const useOutEditorMode = ({
|
||||
exclude,
|
||||
onExitEditMode,
|
||||
}: UseOutEditorModeProps) => {
|
||||
// 处理点击文档其他位置
|
||||
// Process Click Document Other Locations
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
// 如果点击的是编辑器外部,则退出编辑模式
|
||||
// If you click outside the editor, exit editing mode
|
||||
if (
|
||||
editorRef.current &&
|
||||
!editorRef.current.contains(event.target as Node) &&
|
||||
|
||||
@@ -24,28 +24,28 @@ interface UseAddEmptyChunkActionProps {
|
||||
onChunksChange?: (params: { newChunk: Chunk; chunks: Chunk[] }) => void;
|
||||
}
|
||||
/**
|
||||
* 在特定分片后添加新分片的 hook
|
||||
* Add new sharding hook after specific sharding
|
||||
*
|
||||
* 提供在特定分片后添加新分片的功能
|
||||
* Provides the ability to add new shardings after specific shardings
|
||||
*/
|
||||
export const useAddEmptyChunkAction = ({
|
||||
chunks,
|
||||
onChunksChange,
|
||||
}: UseAddEmptyChunkActionProps) => {
|
||||
// 使用ref保存最新的chunks引用
|
||||
// Use ref to save the latest chunks reference
|
||||
const chunksRef = useRef<Chunk[]>(chunks);
|
||||
|
||||
// 每次props.chunks更新时,更新ref
|
||||
// Every time props.chunks is updated, update the ref.
|
||||
useEffect(() => {
|
||||
chunksRef.current = chunks;
|
||||
}, [chunks]);
|
||||
|
||||
/**
|
||||
* 在特定分片后添加新分片
|
||||
* @returns 包含新分片和更新后的分片列表的结果对象
|
||||
* Add new shardings after specific shardings
|
||||
* @Returns the result object containing the new sharding and the updated sharding list
|
||||
*/
|
||||
const handleAddEmptyChunkAfter = (chunk: Chunk) => {
|
||||
// 从ref中获取最新的chunks
|
||||
// Get the latest chunks from the ref
|
||||
const currentChunks = chunksRef.current;
|
||||
const index = currentChunks.findIndex(
|
||||
c =>
|
||||
@@ -79,10 +79,10 @@ export const useAddEmptyChunkAction = ({
|
||||
};
|
||||
|
||||
/**
|
||||
* 在特定分片前添加新分片
|
||||
* Add new shardings before specific shardings
|
||||
*/
|
||||
const handleAddEmptyChunkBefore = (chunk: Chunk) => {
|
||||
// 从ref中获取最新的chunks
|
||||
// Get the latest chunks from the ref
|
||||
const currentChunks = chunksRef.current;
|
||||
const index = currentChunks.findIndex(
|
||||
c =>
|
||||
|
||||
@@ -25,29 +25,29 @@ interface UseDeleteActionProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分片的 hook
|
||||
* Remove sharding hook
|
||||
*
|
||||
* 提供删除特定分片的功能
|
||||
* Provides the ability to remove specific shardings
|
||||
*/
|
||||
export const useDeleteAction = ({
|
||||
chunks,
|
||||
onChunksChange,
|
||||
}: UseDeleteActionProps) => {
|
||||
// 使用ref保存最新的chunks引用
|
||||
// Use ref to save the latest chunks reference
|
||||
const chunksRef = useRef<Chunk[]>(chunks);
|
||||
const { deleteSlice } = useDeleteChunk();
|
||||
|
||||
// 每次props.chunks更新时,更新ref
|
||||
// Every time props.chunks is updated, update the ref.
|
||||
useEffect(() => {
|
||||
chunksRef.current = chunks;
|
||||
}, [chunks]);
|
||||
|
||||
/**
|
||||
* 删除特定分片
|
||||
* Remove specific shardings
|
||||
*/
|
||||
const handleDeleteChunk = useCallback(
|
||||
(chunk: Chunk) => {
|
||||
// 从ref中获取最新的chunks
|
||||
// Get the latest chunks from the ref
|
||||
const currentChunks = chunksRef.current;
|
||||
const updatedChunks = currentChunks.filter(
|
||||
c =>
|
||||
|
||||
@@ -36,7 +36,7 @@ export const useCreateLocalChunk = ({
|
||||
});
|
||||
|
||||
/**
|
||||
* 处理本地分片的创建操作
|
||||
* Handle the creation of local shardings
|
||||
*/
|
||||
const createLocalChunk = async (chunk: Chunk) => {
|
||||
if (!chunk.local_slice_id) {
|
||||
|
||||
@@ -27,7 +27,7 @@ export const useDeleteLocalChunk = ({
|
||||
onChunksChange,
|
||||
}: UseDeleteLocalChunkProps) => {
|
||||
/**
|
||||
* 处理本地分片的删除操作
|
||||
* Handle deletion of local shardings
|
||||
*/
|
||||
const deleteLocalChunk = (chunk: Chunk) => {
|
||||
if (!chunk.local_slice_id) {
|
||||
|
||||
@@ -32,7 +32,7 @@ export const useDeleteRemoteChunk = ({
|
||||
const { deleteSlice } = useDeleteChunk();
|
||||
|
||||
/**
|
||||
* 处理远程分片的删除操作
|
||||
* Handling remotely sharding deletion operations
|
||||
*/
|
||||
const deleteRemoteChunk = async (chunk: Chunk) => {
|
||||
if (!chunk.slice_id) {
|
||||
|
||||
@@ -40,16 +40,16 @@ export const useInitEditor = ({
|
||||
editorProps,
|
||||
onChange,
|
||||
}: UseDocumentEditorProps) => {
|
||||
// 创建编辑器实例
|
||||
// Create an editor instance
|
||||
const editor: Editor | null = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
hardBreak: {
|
||||
// 强制换行
|
||||
// force wrap
|
||||
keepMarks: false,
|
||||
},
|
||||
paragraph: {
|
||||
// 配置段落,避免生成多余的空段落
|
||||
// Configure paragraphs to avoid generating extra empty paragraphs
|
||||
HTMLAttributes: {
|
||||
class: 'text-knowledge-tiptap-editor-paragraph',
|
||||
},
|
||||
@@ -88,30 +88,30 @@ export const useInitEditor = ({
|
||||
}
|
||||
const text = event.clipboardData?.getData('text/plain');
|
||||
|
||||
// 如果粘贴的纯文本中包含换行符
|
||||
// If the pasted plain text contains a newline character
|
||||
if (text?.includes('\n')) {
|
||||
event.preventDefault(); // 阻止默认粘贴行为
|
||||
event.preventDefault(); // Block default paste behavior
|
||||
|
||||
const html = getRenderHtmlContent(text);
|
||||
|
||||
// 将转换后的 HTML 插入编辑器
|
||||
// Insert the converted HTML into the editor
|
||||
editor.chain().focus().insertContent(html).run();
|
||||
|
||||
return true; // 表示我们已处理
|
||||
return true; // It means we have dealt with it.
|
||||
}
|
||||
|
||||
return false; // 使用默认行为
|
||||
return false; // Use default behavior
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 当激活的分片改变时,更新编辑器内容
|
||||
// Update editor content when active sharding changes
|
||||
useEffect(() => {
|
||||
if (!editor || !chunk) {
|
||||
return;
|
||||
}
|
||||
const htmlContent = getRenderHtmlContent(chunk.content || '');
|
||||
// 设置内容,保留换行符
|
||||
// Set content, keep newlines
|
||||
editor.commands.setContent(htmlContent || '', false, {
|
||||
preserveWhitespace: 'full',
|
||||
});
|
||||
|
||||
@@ -37,16 +37,16 @@ export const usePreviewContextMenu = ({
|
||||
onActiveChunkChange,
|
||||
onAddChunk,
|
||||
}: UsePreviewContextMenuProps) => {
|
||||
// 使用ref保存最新的chunks引用
|
||||
// Use ref to save the latest chunks reference
|
||||
const chunksRef = useRef(chunks);
|
||||
const { deleteSlice } = useDeleteChunk();
|
||||
|
||||
// 每次props.chunks更新时,更新ref
|
||||
// Every time props.chunks is updated, update the ref.
|
||||
useEffect(() => {
|
||||
chunksRef.current = chunks;
|
||||
}, [chunks]);
|
||||
|
||||
// 激活特定分片的编辑模式
|
||||
// Activate edit mode for specific shardings
|
||||
const handleActivateEditMode = useCallback(
|
||||
(chunk: Chunk) => {
|
||||
onActiveChunkChange?.(chunk);
|
||||
@@ -54,10 +54,10 @@ export const usePreviewContextMenu = ({
|
||||
[onActiveChunkChange],
|
||||
);
|
||||
|
||||
// 在特定分片前添加新分片
|
||||
// Add new shardings before specific shardings
|
||||
const handleAddChunkBefore = useCallback(
|
||||
(chunk: Chunk) => {
|
||||
// 从ref中获取最新的chunks
|
||||
// Get the latest chunks from the ref
|
||||
const currentChunks = chunksRef.current;
|
||||
const index = currentChunks.findIndex(
|
||||
c =>
|
||||
@@ -86,17 +86,17 @@ export const usePreviewContextMenu = ({
|
||||
|
||||
onChunksChange?.(updatedChunks);
|
||||
|
||||
// 自动激活新分片的编辑模式
|
||||
// Automatically activate editing mode for new shardings
|
||||
onActiveChunkChange?.(newChunk);
|
||||
onAddChunk?.(newChunk);
|
||||
},
|
||||
[onChunksChange, onActiveChunkChange, documentId, onAddChunk],
|
||||
);
|
||||
|
||||
// 在特定分片后添加新分片
|
||||
// Add new shardings after specific shardings
|
||||
const handleAddChunkAfter = useCallback(
|
||||
(chunk: Chunk) => {
|
||||
// 从ref中获取最新的chunks
|
||||
// Get the latest chunks from the ref
|
||||
const currentChunks = chunksRef.current;
|
||||
const index = currentChunks.findIndex(
|
||||
c =>
|
||||
@@ -123,7 +123,7 @@ export const usePreviewContextMenu = ({
|
||||
...currentChunks.slice(index + 1),
|
||||
];
|
||||
|
||||
// 自动激活新分片的编辑模式
|
||||
// Automatically activate editing mode for new shardings
|
||||
onActiveChunkChange?.(newChunk);
|
||||
onChunksChange?.(updatedChunks);
|
||||
onAddChunk?.(newChunk);
|
||||
@@ -131,10 +131,10 @@ export const usePreviewContextMenu = ({
|
||||
[onChunksChange, onActiveChunkChange, documentId, onAddChunk],
|
||||
);
|
||||
|
||||
// 删除特定分片
|
||||
// Remove specific shardings
|
||||
const handleDeleteChunk = useCallback(
|
||||
(chunk: Chunk) => {
|
||||
// 从ref中获取最新的chunks
|
||||
// Get the latest chunks from the ref
|
||||
const currentChunks = chunksRef.current;
|
||||
const updatedChunks = currentChunks.filter(
|
||||
c =>
|
||||
|
||||
@@ -63,7 +63,7 @@ export const useSaveChunk = ({
|
||||
});
|
||||
|
||||
/**
|
||||
* 处理远程分片的保存逻辑
|
||||
* Handling save logic for remote shardings
|
||||
*/
|
||||
const saveRemoteChunk = async (chunk: Chunk) => {
|
||||
if (chunk.content === '') {
|
||||
@@ -74,7 +74,7 @@ export const useSaveChunk = ({
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理本地分片的保存逻辑
|
||||
* Save logic for handling local shardings
|
||||
*/
|
||||
const saveLocalChunk = async (chunk: Chunk) => {
|
||||
if (chunk.content === '') {
|
||||
@@ -85,7 +85,7 @@ export const useSaveChunk = ({
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存分片的主函数
|
||||
* Save the main function of sharding
|
||||
*/
|
||||
const saveChunk = async (chunk: Chunk) => {
|
||||
if (!chunk.local_slice_id) {
|
||||
|
||||
@@ -35,7 +35,7 @@ export const useUpdateRemoteChunk = ({
|
||||
const { updateSlice } = useUpdateChunk();
|
||||
|
||||
/**
|
||||
* 处理远程分片的更新操作
|
||||
* Handling updates for remote sharding
|
||||
*/
|
||||
const updateRemoteChunk = async (chunk: Chunk) => {
|
||||
if (!chunk.slice_id) {
|
||||
|
||||
@@ -28,7 +28,7 @@ export {
|
||||
export { BaseTextKnowledgeEditor } from './scenes/base';
|
||||
export type { Editor } from '@tiptap/react';
|
||||
|
||||
// 新增组件导出
|
||||
// Add component export
|
||||
export { HoverEditBar } from './features/hover-edit-bar/hover-edit-bar';
|
||||
export {
|
||||
EditAction,
|
||||
@@ -37,7 +37,7 @@ export {
|
||||
DeleteAction,
|
||||
} from './features/hover-edit-bar-actions';
|
||||
|
||||
// 事件总线相关导出
|
||||
// Event Bus Dependent Export
|
||||
export {
|
||||
eventBus,
|
||||
createEventBus,
|
||||
|
||||
@@ -46,12 +46,12 @@ export const BaseTextKnowledgeEditor = ({
|
||||
const [chunks, setChunks] = useState<DocumentChunk[]>(initialChunks);
|
||||
const [activeChunk, setActiveChunk] = useState<DocumentChunk | null>(null);
|
||||
|
||||
// 使用编辑器核心功能
|
||||
// Using the core editor functions
|
||||
const { editor } = useInitEditor({
|
||||
chunk: activeChunk,
|
||||
});
|
||||
|
||||
// 退出新增分片功能
|
||||
// Exit the new sharding feature
|
||||
const { saveChunk } = useSaveChunk({
|
||||
chunks,
|
||||
documentId,
|
||||
@@ -63,7 +63,7 @@ export const BaseTextKnowledgeEditor = ({
|
||||
onDeleteChunk,
|
||||
});
|
||||
|
||||
// 监听右键菜单事件
|
||||
// Monitor right-click menu events
|
||||
useEventListener(
|
||||
'previewContextMenuItemAction',
|
||||
useCallback(
|
||||
@@ -88,7 +88,7 @@ export const BaseTextKnowledgeEditor = ({
|
||||
),
|
||||
);
|
||||
|
||||
// 监听悬浮编辑栏事件
|
||||
// Monitor floating edit bar events
|
||||
useEventListener(
|
||||
'hoverEditBarAction',
|
||||
useCallback(
|
||||
|
||||
@@ -24,18 +24,18 @@ export interface ActiveChunkInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理文档中活动的chunk
|
||||
* 使用renderLevel字段来唯一标识chunk的渲染位置
|
||||
* Manage active chunks in documents
|
||||
* Use the renderLevel field to uniquely identify the render location of the chunk
|
||||
*/
|
||||
export const useActiveChunk = () => {
|
||||
// 存储活动的chunk和它的renderLevel
|
||||
// Store the active chunk and its renderLevel
|
||||
const [activeChunkInfo, setActiveChunkInfo] = useState<ActiveChunkInfo>({
|
||||
chunk: null,
|
||||
renderLevel: null,
|
||||
});
|
||||
|
||||
/**
|
||||
* 清除活动chunk信息
|
||||
* Clear active chunks
|
||||
*/
|
||||
const clearActiveChunk = () => {
|
||||
setActiveChunkInfo({
|
||||
@@ -45,8 +45,8 @@ export const useActiveChunk = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置活动chunk和它的renderLevel
|
||||
* 在用户交互(如双击)时使用
|
||||
* Set the active chunk and its renderLevel
|
||||
* Use during user interaction (e.g. double-clicking)
|
||||
*/
|
||||
const setActiveChunkWithLevel = (chunk: LevelDocumentTreeNode) => {
|
||||
if (!chunk.renderLevel) {
|
||||
@@ -61,7 +61,7 @@ export const useActiveChunk = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查给定的chunk是否是当前活动的chunk
|
||||
* Checks whether the given chunk is the currently active chunk
|
||||
*/
|
||||
const isActiveChunk = (renderLevel: string | undefined) => {
|
||||
if (!renderLevel) {
|
||||
|
||||
@@ -24,19 +24,19 @@ export interface ActiveChunkInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理文档中具有相同ID的chunk的渲染路径
|
||||
* 通过为每个chunk实例分配唯一的渲染路径,解决重复ID的问题
|
||||
* Manage the rendering path for chunks with the same ID in a document
|
||||
* Solve the problem of duplicate IDs by assigning a unique render path to each chunk instance
|
||||
*/
|
||||
export const useChunkRenderPath = () => {
|
||||
// 存储活动的chunk和它的渲染路径
|
||||
// Store the active chunk and its render path
|
||||
const [activeChunkInfo, setActiveChunkInfo] = useState<ActiveChunkInfo>({
|
||||
chunk: null,
|
||||
renderPath: null,
|
||||
});
|
||||
|
||||
/**
|
||||
* 设置活动chunk,但不设置渲染路径
|
||||
* 通常在外部逻辑中使用,如usePreviewContextMenu
|
||||
* Set the active chunk, but not the render path
|
||||
* Usually used in external logic, such as usePreviewContextMenu
|
||||
*/
|
||||
const setActiveChunk = (chunk: LevelDocumentChunk | null) => {
|
||||
setActiveChunkInfo(prev => ({
|
||||
@@ -46,7 +46,7 @@ export const useChunkRenderPath = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 清除活动chunk信息
|
||||
* Clear active chunks
|
||||
*/
|
||||
const clearActiveChunk = () => {
|
||||
setActiveChunkInfo({
|
||||
@@ -56,8 +56,8 @@ export const useChunkRenderPath = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置活动chunk和它的渲染路径
|
||||
* 在用户交互(如双击)时使用
|
||||
* Set the active chunk and its rendering path
|
||||
* Use during user interaction (e.g. double-clicking)
|
||||
*/
|
||||
const setActiveChunkWithPath = (
|
||||
chunk: LevelDocumentChunk,
|
||||
@@ -70,14 +70,14 @@ export const useChunkRenderPath = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查给定的chunk和渲染路径是否匹配当前活动的chunk
|
||||
* Checks whether the given chunk and rendering path match the currently active chunk
|
||||
*/
|
||||
const isActiveChunk = (chunkId: string, renderPath: string) =>
|
||||
chunkId === activeChunkInfo.chunk?.text_knowledge_editor_chunk_uuid &&
|
||||
renderPath === activeChunkInfo.renderPath;
|
||||
|
||||
/**
|
||||
* 为chunk生成唯一的渲染路径
|
||||
* Generate a unique render path for chunks
|
||||
*/
|
||||
const generateRenderPath = (basePath: string, chunkId: string) =>
|
||||
`${basePath}-${chunkId}`;
|
||||
|
||||
@@ -19,8 +19,8 @@ import { useEffect } from 'react';
|
||||
import { createLocateChunkId } from '../../services/locate-segment';
|
||||
|
||||
/**
|
||||
* 滚动到选中的元素
|
||||
* @param selectionIDs 选中的元素ID数组
|
||||
* Scroll to the selected element
|
||||
* @Param selectionIDs array of selected element IDs
|
||||
*/
|
||||
export const useScrollToSelection = (selectionIDs?: string[]) => {
|
||||
useEffect(() => {
|
||||
|
||||
@@ -66,7 +66,7 @@ export const LevelTextKnowledgeEditor: React.FC<
|
||||
[chunks],
|
||||
);
|
||||
|
||||
// 使用活动chunk hook
|
||||
// Using active chunk hooks
|
||||
const {
|
||||
activeChunkInfo,
|
||||
clearActiveChunk,
|
||||
@@ -74,12 +74,12 @@ export const LevelTextKnowledgeEditor: React.FC<
|
||||
isActiveChunk,
|
||||
} = useActiveChunk();
|
||||
|
||||
// 使用编辑器核心功能
|
||||
// Using the core editor functions
|
||||
const { editor } = useInitEditor({
|
||||
chunk: activeChunkInfo.chunk,
|
||||
});
|
||||
|
||||
// 分片功能
|
||||
// Sharding function
|
||||
const { saveChunk } = useSaveChunk({
|
||||
chunks: initialChunks,
|
||||
documentId,
|
||||
@@ -92,10 +92,10 @@ export const LevelTextKnowledgeEditor: React.FC<
|
||||
},
|
||||
});
|
||||
|
||||
// 使用滚动到选中元素的hook
|
||||
// Use the hook that scrolls to the selected element
|
||||
useScrollToSelection(selectionIDs);
|
||||
|
||||
// 监听右键菜单事件
|
||||
// Monitor right-click menu events
|
||||
useEventListener(
|
||||
'previewContextMenuItemAction',
|
||||
useCallback(({ type, targetChunk, chunks: newChunks }) => {
|
||||
@@ -109,7 +109,7 @@ export const LevelTextKnowledgeEditor: React.FC<
|
||||
}, []),
|
||||
);
|
||||
|
||||
// 监听悬浮编辑栏事件
|
||||
// Monitor floating edit bar events
|
||||
useEventListener(
|
||||
'hoverEditBarAction',
|
||||
useCallback(({ type, targetChunk, chunks: newChunks }) => {
|
||||
@@ -231,7 +231,7 @@ const RenderContent = ({
|
||||
].includes(levelDocumentTree.type) ? (
|
||||
<div key={levelDocumentTree.text_knowledge_editor_chunk_uuid}>
|
||||
{(() => {
|
||||
// 检查这个chunk是否是当前活动的chunk,使用renderLevel字段
|
||||
// Check whether this chunk is the currently active chunk, using the renderLevel field
|
||||
const chunkIsActive = isActiveChunk(levelDocumentTree.renderLevel);
|
||||
|
||||
if (chunkIsActive) {
|
||||
@@ -261,7 +261,7 @@ const RenderContent = ({
|
||||
previewContextMenuItemsContributes
|
||||
}
|
||||
onActivateEditMode={activedChunk => {
|
||||
// 设置活动chunk,使用chunk自身的renderLevel
|
||||
// Set the active chunk, using the renderLevel of the chunk itself
|
||||
setActiveChunkWithLevel(
|
||||
activedChunk as LevelDocumentTreeNode,
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ const getChildren = (
|
||||
}
|
||||
}, []);
|
||||
|
||||
/** 兜底情况,如果 idp 的结果没有标题,则补上标题 */
|
||||
/** Bottom line, if the result of idp has no title, add the title */
|
||||
export const withTitle = (
|
||||
segments: LevelDocumentChunk[],
|
||||
title?: string,
|
||||
|
||||
@@ -24,7 +24,7 @@ export type LevelDocumentTreeNode = Omit<ILevelSegment, 'children' | 'parent'> &
|
||||
Chunk & {
|
||||
parent?: string;
|
||||
children?: LevelDocumentTreeNode[];
|
||||
renderLevel?: string; // 用于唯一标识chunk的渲染路径
|
||||
renderLevel?: string; // Rendering path used to uniquely identify chunks
|
||||
};
|
||||
|
||||
export type LevelDocumentTree = LevelDocumentTreeNode[];
|
||||
|
||||
@@ -31,7 +31,7 @@ export const createLocalChunk = (props: { sequence: string }): Chunk => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新本地分片
|
||||
* Update local sharding
|
||||
*/
|
||||
export const updateLocalChunk = ({
|
||||
chunks,
|
||||
@@ -44,34 +44,34 @@ export const updateLocalChunk = ({
|
||||
}): Chunk[] =>
|
||||
chunks.map(c => (c.local_slice_id === localChunkSliceId ? newChunk : c));
|
||||
|
||||
// 删除本地分片
|
||||
// Delete local sharding
|
||||
export const deleteLocalChunk = (
|
||||
chunks: Chunk[],
|
||||
localChunkSliceId: string,
|
||||
): Chunk[] => chunks.filter(c => c.local_slice_id !== localChunkSliceId);
|
||||
|
||||
/**
|
||||
* 更新文档分片内容
|
||||
* Update document sharding content
|
||||
*/
|
||||
export const updateChunkContent = (chunk: Chunk, content: string): Chunk => ({
|
||||
...chunk,
|
||||
content,
|
||||
});
|
||||
|
||||
// 删除远程分片
|
||||
// Delete remote sharding
|
||||
export const deleteRemoteChunk = (
|
||||
chunks: Chunk[],
|
||||
remoteChunkSliceId: string,
|
||||
): Chunk[] => chunks.filter(c => c.slice_id !== remoteChunkSliceId);
|
||||
|
||||
/**
|
||||
* 更新chunks
|
||||
* Update chunks
|
||||
*/
|
||||
export const updateChunks = (chunks: Chunk[], chunk: Chunk): Chunk[] =>
|
||||
chunks.map(c => (c.slice_id === chunk.slice_id ? chunk : c));
|
||||
|
||||
/**
|
||||
* 创建远程分片
|
||||
* Create remote sharding
|
||||
*/
|
||||
export const createRemoteChunk = (props: {
|
||||
sequence: string;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { type Chunk } from '@/text-knowledge-editor/types/chunk';
|
||||
|
||||
/**
|
||||
* 更新文档分片内容
|
||||
* Update document sharding content
|
||||
*/
|
||||
export const updateChunkContent = (chunk: Chunk, content: string): Chunk => ({
|
||||
...chunk,
|
||||
@@ -25,13 +25,13 @@ export const updateChunkContent = (chunk: Chunk, content: string): Chunk => ({
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新chunks
|
||||
* Update chunks
|
||||
*/
|
||||
export const updateChunks = (chunks: Chunk[], chunk: Chunk): Chunk[] =>
|
||||
chunks.map(c => (c.slice_id === chunk.slice_id ? chunk : c));
|
||||
|
||||
/**
|
||||
* 获取激活的分片
|
||||
* Get active sharding
|
||||
*/
|
||||
export const getActiveChunk = (
|
||||
chunks: Chunk[],
|
||||
@@ -44,15 +44,15 @@ export const getActiveChunk = (
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理编辑器输出的HTML内容
|
||||
* 移除不必要的外层<p>标签,保持与原始内容格式一致
|
||||
* Process the HTML content output by the editor
|
||||
* Remove unnecessary outer < p > tags to maintain the original content format
|
||||
*/
|
||||
export const processEditorContent = (content: string): string => {
|
||||
if (!content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 如果内容被<p>标签包裹,并且只有一个<p>标签
|
||||
// If the content is wrapped with < p > tags, and there is only one < p > tag
|
||||
const singleParagraphMatch = content.match(/^<p>(.*?)<\/p>$/s);
|
||||
if (singleParagraphMatch) {
|
||||
return singleParagraphMatch[1];
|
||||
|
||||
@@ -18,7 +18,7 @@ import classNames from 'classnames';
|
||||
|
||||
export const getEditorTableClassname = () =>
|
||||
classNames(
|
||||
// 表格样式
|
||||
// table style
|
||||
'[&_table]:border-collapse [&_table]:m-0 [&_table]:w-full [&_table]:table-fixed [&_table]:overflow-hidden [&_table]:text-[0.9em]',
|
||||
'[&_table_td]:border [&_table_th]:border [&_table_td]:border-[#ddd] [&_table_th]:border-[#ddd]',
|
||||
'[&_table_td]:p-2 [&_table_th]:p-2',
|
||||
|
||||
@@ -18,12 +18,12 @@ import classNames from 'classnames';
|
||||
|
||||
export const getEditorWordsCls = () =>
|
||||
classNames(
|
||||
// 换行
|
||||
// line feed
|
||||
'[&_p]:break-words [&_p]:whitespace-pre-wrap',
|
||||
// 保留所有空格和换行符
|
||||
// Keep all spaces and line breaks
|
||||
'[&_.ProseMirror_*]:break-words [&_.ProseMirror_*]:whitespace-pre-wrap',
|
||||
// 段落
|
||||
// paragraph
|
||||
'[&_.editor-paragraph]:min-h-[1.5em] [&_.editor-paragraph]:leading-normal',
|
||||
// 空段落
|
||||
// Empty paragraph
|
||||
'[&_.editor-paragraph:empty]:min-h-[1.5em] [&_.editor-paragraph:empty]:block',
|
||||
);
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
import { type Editor } from '@tiptap/react';
|
||||
|
||||
/**
|
||||
* 获取编辑器内容
|
||||
* 返回用户真实编辑的内容,移除TipTap自动添加的外层<p>标签
|
||||
* Get editor content
|
||||
* Return the user's actual edited content and remove the outer < p > tag automatically added by TipTap
|
||||
*/
|
||||
export const getEditorContent = (editor: Editor | null) => {
|
||||
if (!editor) {
|
||||
@@ -29,38 +29,38 @@ export const getEditorContent = (editor: Editor | null) => {
|
||||
|
||||
const doc = removeEditorWrapperParagraph(content);
|
||||
|
||||
// 返回处理后的HTML
|
||||
// Returns the processed HTML.
|
||||
return doc;
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理编辑器输出的HTML内容
|
||||
* 移除不必要的外层<p>标签,保持与原始内容格式一致
|
||||
* Process the HTML content output by the editor
|
||||
* Remove unnecessary outer < p > tags to maintain the original content format
|
||||
*/
|
||||
export const removeEditorWrapperParagraph = (content: string): string => {
|
||||
if (!content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 使用DOM解析器来处理HTML
|
||||
// Using a DOM parser to process HTML
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(content, 'text/html');
|
||||
|
||||
// 找到所有编辑器生成的p标签
|
||||
// Find all editor-generated p tags
|
||||
const generatedParagraphs = doc.querySelectorAll(
|
||||
'p.text-knowledge-tiptap-editor-paragraph',
|
||||
);
|
||||
|
||||
// 替换这些p标签为它们的内容
|
||||
// Replace these p tags with their content
|
||||
generatedParagraphs.forEach(p => {
|
||||
const parent = p.parentNode;
|
||||
if (parent) {
|
||||
// 创建一个文档片段来存储p标签的内容
|
||||
// Create a document fragment to store the contents of the p tag
|
||||
const fragment = document.createDocumentFragment();
|
||||
while (p.firstChild) {
|
||||
fragment.appendChild(p.firstChild);
|
||||
}
|
||||
// 用内容替换p标签
|
||||
// Replace p tags with content
|
||||
parent.replaceChild(fragment, p);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 编辑器对/n不会换行,所以需要转换为<br />标签
|
||||
* The editor doesn't wrap/n, so it needs to be converted to a < br/> tag
|
||||
*/
|
||||
export const getInitEditorContent = (content: string) => {
|
||||
if (content === '') {
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
import { escapeHtml } from '@/text-knowledge-editor/utils/escape-html';
|
||||
|
||||
/**
|
||||
* 获取渲染后的HTML内容
|
||||
* Get the rendered HTML content
|
||||
*/
|
||||
export const getRenderHtmlContent = (content: string) => {
|
||||
if (content === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 转义HTML,只允许白名单中的标签
|
||||
// Escape HTML, allowing only whitelisted tags
|
||||
const htmlContent = escapeHtml(content);
|
||||
|
||||
// 编辑器对/n不会换行,所以需要转换为<br />标签
|
||||
// The editor doesn't wrap/n, so it needs to be converted to a < br/> tag
|
||||
return htmlContent.replace(/\n/g, '<br />');
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ import { type Chunk } from '@/text-knowledge-editor/types/chunk';
|
||||
import { processEditorContent } from '../inner/document-editor.service';
|
||||
|
||||
/**
|
||||
* 判断内容是否改变
|
||||
* Determine whether the content has changed
|
||||
*/
|
||||
export const isEditorContentChange = (
|
||||
chunks: Chunk[],
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* html白名单,防止XSS攻击
|
||||
* HTML whitelist to prevent XSS attacks
|
||||
*/
|
||||
|
||||
// 默认允许的HTML标签白名单
|
||||
// Whitelist of HTML tags allowed by default
|
||||
const DEFAULT_ALLOWED_TAGS = [
|
||||
'img',
|
||||
'table',
|
||||
@@ -35,10 +35,10 @@ const DEFAULT_ALLOWED_TAGS = [
|
||||
];
|
||||
|
||||
/**
|
||||
* 转义HTML,只允许白名单中的标签
|
||||
* @param unsafe 不安全的HTML字符串
|
||||
* @param allowedTags 允许的HTML标签数组,默认为DEFAULT_ALLOWED_TAGS
|
||||
* @returns 转义后的HTML字符串
|
||||
* Escape HTML, allowing only whitelisted tags
|
||||
* @param unsafe HTML string
|
||||
* @Param allowedTags Array of HTML tags allowed, default to DEFAULT_ALLOWED_TAGS
|
||||
* @Returns the escaped HTML string
|
||||
*/
|
||||
export function escapeHtml(
|
||||
unsafe: string,
|
||||
@@ -48,7 +48,7 @@ export function escapeHtml(
|
||||
return '';
|
||||
}
|
||||
|
||||
// 构建正则表达式模式
|
||||
// Building regular expression patterns
|
||||
const allowedTagsPattern = allowedTags.join('|');
|
||||
const tagRegex = new RegExp(
|
||||
`<(?!(${allowedTagsPattern})\\b[^>]*>|\\/(?:${allowedTagsPattern})>)`,
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
|
||||
import { getKnowledgeIDEQuery } from '@coze-data/knowledge-common-services/use-case';
|
||||
|
||||
/** knowledge 模块专用的 useNavigate,用于持久化公共 query 参数 */
|
||||
/** useNavigate dedicated to the knowledge module for persisting common query parameters */
|
||||
export const useKnowledgeNavigate: typeof useNavigate = () => {
|
||||
const navigate = useNavigate();
|
||||
const knowledgePageQuery = getKnowledgeIDEQuery();
|
||||
|
||||
@@ -30,6 +30,6 @@ export const getKnowledgeIDEQuery = (): KnowledgeIDEQuery => {
|
||||
agent_id: queryParams.get('agent_id'),
|
||||
page_mode: queryParams.get('page_mode') as KnowledgeIDEQuery['page_mode'],
|
||||
};
|
||||
// 过滤掉空值,避免产生多余的 querystring
|
||||
// Filter out null values to avoid generating extra querystrings.
|
||||
return Object.fromEntries(Object.entries(knowledgeQuery).filter(e => !!e[1]));
|
||||
};
|
||||
|
||||
@@ -26,15 +26,15 @@ import {
|
||||
|
||||
export enum FilterPhotoType {
|
||||
/**
|
||||
* 全部
|
||||
* all
|
||||
*/
|
||||
All = 'All',
|
||||
/**
|
||||
* 已标注
|
||||
* marked
|
||||
*/
|
||||
HasCaption = 'HasCaption',
|
||||
/**
|
||||
* 未标注
|
||||
* unmarked
|
||||
*/
|
||||
NoCaption = 'NoCaption',
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export interface KnowledgePreviewState {
|
||||
searchValue: string;
|
||||
curDocId: string;
|
||||
/**
|
||||
* 图片类型是否已标注
|
||||
* Is the image type marked?
|
||||
*/
|
||||
photoFilterValue: FilterPhotoType;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export interface IParams {
|
||||
agentID?: string;
|
||||
actionType?: ActionType;
|
||||
initialTab?: 'structure' | 'draft' | 'online';
|
||||
/** 作用是跳转上传页时能在 url 里带上抖音标记,以在上传页做视图区分 */
|
||||
/** The function is to bring the Douyin mark in the url when jumping to the upload page to distinguish the views on the upload page */
|
||||
isDouyinBot?: boolean;
|
||||
pageMode?: 'modal' | 'normal';
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ interface DatasetStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* 只适用于 bot 单 agent 模式
|
||||
* Only works in bot single agent mode
|
||||
*/
|
||||
export const useDatasetStore = create<DatasetStore>()(
|
||||
devtools(
|
||||
|
||||
@@ -61,17 +61,17 @@ export const AutoGenerateButton: React.FC<AutoGenerateButtonProps> = ({
|
||||
onProgress(false);
|
||||
}
|
||||
};
|
||||
// 如果没有 caption,则不用confirm
|
||||
// If there is no caption, do not confirm
|
||||
if (!currentValue) {
|
||||
await generateCaption();
|
||||
return;
|
||||
}
|
||||
UIModal.warning({
|
||||
// 必填参数,统一 confirm modal 的样式
|
||||
// Required parameters to confirm modal style
|
||||
className: styles['confirm-modal'],
|
||||
closeIcon: <IconCloseKnowledge />,
|
||||
|
||||
// 自定义参数
|
||||
// custom parameters
|
||||
title: I18n.t('knowledge_photo_021'),
|
||||
content: I18n.t('knowledge_photo_022'),
|
||||
icon: <IconWarningSize24 />,
|
||||
|
||||
@@ -94,14 +94,14 @@ export const HeaderTags = ({
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{/* 图片来源 */}
|
||||
{/* Image source */}
|
||||
{formatType === FormatType.Image && (
|
||||
<Tag size="mini" color="primary">
|
||||
{I18n.t('dataset_detail_source_local')}
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{/* 图片数量 */}
|
||||
{/* number of pictures */}
|
||||
{formatType === FormatType.Image && !!dataSetDetail?.doc_count && (
|
||||
<Tag size="mini" color="primary">
|
||||
{I18n.t('knowledge_photo_015', {
|
||||
@@ -110,7 +110,7 @@ export const HeaderTags = ({
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{/* 未添加文档时不展示 */}
|
||||
{/* Do not display when no document is added */}
|
||||
{formatType !== FormatType.Image && !!dataSetDetail?.doc_count && (
|
||||
<>
|
||||
{/* slice count */}
|
||||
|
||||
@@ -111,7 +111,7 @@ export const KnowledgeModalNavBar: React.FC<KnowledgeModalNavBarProps> = ({
|
||||
<div className={styles.toolbar}>
|
||||
<Space spacing={12}>
|
||||
{isImageFormat ? <PhotoFilter /> : null}
|
||||
{/* 导入按钮 */}
|
||||
{/* import button */}
|
||||
{canEdit ? importKnowledgeSourceButton : null}
|
||||
{actionButtons}
|
||||
</Space>
|
||||
|
||||
@@ -124,7 +124,7 @@ export const KnowledgeIDENavBar = ({
|
||||
].includes(unitType),
|
||||
[unitType],
|
||||
);
|
||||
// link 或者 action 二选一存在时才展示
|
||||
// Link or action will only be displayed when one exists
|
||||
const showTableConfigButton =
|
||||
(isShowLinkUrl || canEdit) && isTableFormat && documentList?.length;
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export const usePhotoDetailModal = (params: UsePhotoDetailModalParams) => {
|
||||
|
||||
const status = progressMap[document_id || '']?.status || originStatus;
|
||||
|
||||
// 处理中获处理失败的photo,不允许更新 caption
|
||||
// The photo that failed to be processed is not allowed to update the caption.
|
||||
const disableUpdate =
|
||||
status === DocumentStatus.Processing ||
|
||||
status === DocumentStatus.Failed ||
|
||||
@@ -86,7 +86,7 @@ export const usePhotoDetailModal = (params: UsePhotoDetailModalParams) => {
|
||||
}
|
||||
}, [
|
||||
visible,
|
||||
// 切换图片时需要更新初始状态
|
||||
// The initial state needs to be updated when switching pictures.
|
||||
document_id,
|
||||
]);
|
||||
|
||||
@@ -136,12 +136,12 @@ export const usePhotoDetailModal = (params: UsePhotoDetailModalParams) => {
|
||||
<div className={styles['modal-content']}>
|
||||
<div className={styles['photo-large']}>
|
||||
<Image
|
||||
// 仅设置高度,宽度会按图片原比例自动缩放
|
||||
// Only set the height, and the width will be automatically scaled according to the original scale of the picture.
|
||||
height={300}
|
||||
src={photo.url}
|
||||
/>
|
||||
{
|
||||
// 未找到,或者为第一个,不展示 pre 按钮
|
||||
// Not found, or for the first one, the pre button is not displayed
|
||||
currentIndex < 1 ? null : (
|
||||
<IconButton
|
||||
icon={<Icon svg={<SvgArrowLeft />} />}
|
||||
@@ -158,7 +158,7 @@ export const usePhotoDetailModal = (params: UsePhotoDetailModalParams) => {
|
||||
)
|
||||
}
|
||||
{
|
||||
// 未找到,或者为最后一个,不展示 pre 按钮
|
||||
// Not found, or for the last one, the pre button is not displayed
|
||||
currentIndex === -1 ||
|
||||
currentIndex === photoList.length - 1 ? null : (
|
||||
<IconButton
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IconUnitsTable, IconUnitsFile } from '@coze-arch/bot-icons';
|
||||
|
||||
import { IconWithSuffix } from './suffix';
|
||||
|
||||
// 获取 icon
|
||||
// Get icon
|
||||
export const RenderDocumentIcon = ({
|
||||
formatType,
|
||||
sourceType,
|
||||
|
||||
@@ -121,7 +121,7 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
setCurrentPhotoId,
|
||||
reload: reloadAsync,
|
||||
onCancel: () => {
|
||||
// 重置url参数
|
||||
// Reset url parameters
|
||||
resetUrlQueryParams();
|
||||
},
|
||||
onSubmit: () => {
|
||||
@@ -129,12 +129,12 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
},
|
||||
});
|
||||
|
||||
// 手动控制 data 加载时机
|
||||
// Manually control data loading timing
|
||||
useEffect(() => {
|
||||
if (dataSetDetail?.dataset_id) {
|
||||
reloadAsync();
|
||||
|
||||
// 重新加载时,回到最顶部
|
||||
// When reloading, return to the top
|
||||
ref.current?.scrollTo?.({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
@@ -142,7 +142,7 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
}
|
||||
}, [searchValue, photoFilterValue, dataSetDetail?.dataset_id]);
|
||||
|
||||
// 自动打开编辑弹窗
|
||||
// Automatically open the editing pop-up window
|
||||
useEffect(() => {
|
||||
if (shouldAutoOpenDetailModal) {
|
||||
if (firstAutoOpenEditDocumentId) {
|
||||
@@ -182,10 +182,10 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
caption: originCaption,
|
||||
status: originStatus,
|
||||
} = item;
|
||||
// 此处使用 progressMap 可以保持不断刷新直至完成
|
||||
// Use progressMap here to keep refreshing until completion
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
const status = progressMap[document_id]?.status || originStatus;
|
||||
// 使用 progressMap 获取最新的caption
|
||||
// Use progressMap to get the latest caption
|
||||
const caption =
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
(progressMap[document_id] as ProgressItem & PhotoInfo)
|
||||
@@ -202,11 +202,11 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
|
||||
const handleDelete = () => {
|
||||
UIModal.error({
|
||||
// 必填参数,统一 confirm modal 的样式
|
||||
// Required parameters to confirm modal style
|
||||
className: styles['confirm-modal'],
|
||||
closeIcon: <IconCloseKnowledge />,
|
||||
|
||||
// 自定义参数
|
||||
// custom parameters
|
||||
title: I18n.t('kl2_007'),
|
||||
content: I18n.t(
|
||||
'dataset_detail_table_deleteModel_description',
|
||||
@@ -247,7 +247,7 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
const isAudiFailed =
|
||||
originStatus === DocumentStatus.AuditFailed;
|
||||
const getCaption = () => {
|
||||
// 违规图片
|
||||
// Illegal pictures
|
||||
if (isAudiFailed) {
|
||||
return (
|
||||
<span>
|
||||
@@ -256,7 +256,7 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
);
|
||||
}
|
||||
|
||||
// 处理失败
|
||||
// Processing failed
|
||||
if (status === DocumentStatus.Failed) {
|
||||
return (
|
||||
<span className={styles['failed-tag']}>
|
||||
@@ -265,7 +265,7 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
);
|
||||
}
|
||||
|
||||
// 处理中
|
||||
// Processing
|
||||
if (status === DocumentStatus.Processing) {
|
||||
return (
|
||||
<span className={styles['processing-tag']}>
|
||||
@@ -274,12 +274,12 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
);
|
||||
}
|
||||
|
||||
// 已标注
|
||||
// marked
|
||||
if (hasCaption) {
|
||||
return caption;
|
||||
}
|
||||
|
||||
// 未标注
|
||||
// unmarked
|
||||
return I18n.t('knowledge_photo_016');
|
||||
};
|
||||
|
||||
@@ -295,7 +295,7 @@ export const ImageKnowledgeWorkspace: FC<
|
||||
) : (
|
||||
<Image
|
||||
src={url}
|
||||
// 仅设置宽度,高度会按图片原比例自动缩放
|
||||
// Only set the width, and the height will be automatically scaled according to the original scale of the picture.
|
||||
width={222}
|
||||
preview={false}
|
||||
onClick={handleEdit}
|
||||
|
||||
@@ -60,7 +60,7 @@ export const usePhotoList = (
|
||||
? true
|
||||
: filterPhotoType === FilterPhotoType.NoCaption
|
||||
? false
|
||||
: // 传 undefined 代表返回全部
|
||||
: // Pass undefined to return all
|
||||
undefined,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ export const ImportKnowledgeSourceButton = ({
|
||||
onSourceChange(unitType);
|
||||
return;
|
||||
}
|
||||
/** 默认跳转到upload */
|
||||
/** Default jump to upload */
|
||||
const formatType = dataSetDetail?.format_type;
|
||||
const docID = documentList?.[0]?.document_id;
|
||||
const params: Record<string, string> = {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { type Dataset, type DocumentInfo } from '@coze-arch/bot-api/knowledge';
|
||||
import { DocumentStatus, FormatType } from '@coze-arch/bot-api/knowledge';
|
||||
|
||||
/**
|
||||
* 处理表格类型数据集的禁用提示
|
||||
* Disable hints for working with table-type datasets
|
||||
*/
|
||||
export const getTableFormatTooltip = (documentList: DocumentInfo[]): string => {
|
||||
const docInfo = documentList?.[0];
|
||||
@@ -44,7 +44,7 @@ export const getTableFormatTooltip = (documentList: DocumentInfo[]): string => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理默认类型数据集的禁用提示
|
||||
* Disable hints for handling default type datasets
|
||||
*/
|
||||
export const getDefaultFormatTooltip = (dataSetDetail: Dataset): string => {
|
||||
// @ts-expect-error -- linter-disable-autofix
|
||||
@@ -60,10 +60,10 @@ export const getDefaultFormatTooltip = (dataSetDetail: Dataset): string => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建按钮禁用时的提示文本
|
||||
* @param dataSetDetail - 数据集详情
|
||||
* @param documentList - 文档列表
|
||||
* @returns 提示文本
|
||||
* Create prompt text when button is disabled
|
||||
* @param dataSetDetail - Dataset Details
|
||||
* @param documentList - Document List
|
||||
* @Returns prompt text
|
||||
*/
|
||||
export const createBtnDisableToolTip = (
|
||||
dataSetDetail: Dataset,
|
||||
|
||||
@@ -121,12 +121,12 @@ export const getUpdatedDataset = (
|
||||
actionType: ActionType,
|
||||
dataSetDetail: Dataset,
|
||||
): KnowledgeInfo[] => {
|
||||
// 更新后的bot知识库
|
||||
// Updated bot knowledge base
|
||||
let updatedDatasetList: KnowledgeInfo[] = [];
|
||||
// 原本的bot知识库内容
|
||||
// The original bot knowledge base content
|
||||
let originDataset: KnowledgeInfo[] = [];
|
||||
|
||||
// 兼容json版本的dataset,FG全量后删除
|
||||
// Compatible with the json version of dataset, delete it after FG is full
|
||||
if ('dataset' in dataset) {
|
||||
originDataset = dataset?.dataset ?? [];
|
||||
} else {
|
||||
@@ -147,7 +147,7 @@ export const getUpdatedDataset = (
|
||||
return updatedDatasetList;
|
||||
};
|
||||
|
||||
// 更新bot知识库逻辑
|
||||
// Update bot knowledge base logic
|
||||
export const handleDatasetUpdate = async ({
|
||||
botInfo,
|
||||
botId,
|
||||
@@ -181,7 +181,7 @@ export const handleDatasetUpdate = async ({
|
||||
updateSuccess();
|
||||
};
|
||||
|
||||
// 根据不同botInfo信息 获取不同的bot原有的dataset
|
||||
// Get the original dataset of different bots according to different botInfo information
|
||||
export const getDatasetInfo = (
|
||||
botInfo: GetDraftBotInfoAgwData | undefined,
|
||||
agentId: string,
|
||||
|
||||
@@ -96,7 +96,7 @@ export const useBeforeKnowledgeIDEClose = ({
|
||||
};
|
||||
|
||||
const handleBotIdeBack = () => {
|
||||
// Bot IDE检查是否有绑定knowledge,如果有绑定知识库正常关闭,没有绑定确认提示处理
|
||||
// Bot IDE checks whether there is binding knowledge. If there is binding knowledge base, it will be closed normally, and there is no binding confirmation prompt.
|
||||
if (hasAddDataset) {
|
||||
Modal.confirm({
|
||||
title: I18n.t('bot_ide_knowledge_confirm_title'),
|
||||
@@ -124,17 +124,17 @@ export const useBeforeKnowledgeIDEClose = ({
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// 无论成功无论都跳转一次
|
||||
// Jump once regardless of success
|
||||
handleFullModalBack();
|
||||
}
|
||||
},
|
||||
onCancel: () => {
|
||||
// 取消,正常跳转
|
||||
// Cancel, jump normally
|
||||
handleFullModalBack();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 正常绑定不做弹窗拦截
|
||||
// Normal binding does not do pop-up interception
|
||||
handleFullModalBack();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,7 +92,7 @@ export const TableKnowledgeWorkspace = ({
|
||||
slices,
|
||||
]);
|
||||
|
||||
// 创建 UI Context 值
|
||||
// Creating UI Context Values
|
||||
const uiContextValue = {
|
||||
tableViewRef,
|
||||
isLoadingMoreSliceList,
|
||||
@@ -100,7 +100,7 @@ export const TableKnowledgeWorkspace = ({
|
||||
isShowAddBtn,
|
||||
};
|
||||
|
||||
// 创建数据 Context 值
|
||||
// Creating Data Context Values
|
||||
const dataContextValue = {
|
||||
sliceListData: sliceListData || { list: [], total: 0 },
|
||||
curIndex,
|
||||
@@ -108,7 +108,7 @@ export const TableKnowledgeWorkspace = ({
|
||||
delSliceIds,
|
||||
};
|
||||
|
||||
// 创建操作 Context 值
|
||||
// Create Action Context Value
|
||||
const actionsContextValue = {
|
||||
setCurIndex,
|
||||
setCurSliceId,
|
||||
|
||||
@@ -38,7 +38,7 @@ import { useTableUI } from '../context/table-ui-context';
|
||||
import { useTableData } from '../context/table-data-context';
|
||||
import { useTableActions } from '../context/table-actions-context';
|
||||
|
||||
// 表格内容组件
|
||||
// Table Content Component
|
||||
const TableContent = () => {
|
||||
const knowledgeIDEBiz = useKnowledgeParamsStore(state => state.params.biz);
|
||||
const documentList = useKnowledgeStore(state => state.documentList);
|
||||
@@ -55,14 +55,14 @@ const TableContent = () => {
|
||||
|
||||
const canEdit = Boolean(useKnowledgeStore(state => state.canEdit));
|
||||
|
||||
// 删除切片弹窗
|
||||
// Delete slice pop-up
|
||||
const { deleteSliceModalNode, openDeleteSliceModal } = useDeleteSliceModal();
|
||||
|
||||
// 编辑slice弹窗
|
||||
// Edit slice pop-up
|
||||
const { tableSegmentModalNode, openTableSegmentModal } =
|
||||
useTableSegmentModal();
|
||||
|
||||
// 获取表格操作方法
|
||||
// Get table manipulation method
|
||||
const { deleteSlice, rowUpdateSliceData, modalEditSlice } =
|
||||
useTableSliceOperations({
|
||||
openDeleteSliceModal,
|
||||
@@ -71,7 +71,7 @@ const TableContent = () => {
|
||||
|
||||
const { tableH } = useTableHeight();
|
||||
|
||||
// 如果没有数据,直接返回空
|
||||
// If there is no data, return to empty directly
|
||||
if (!slices?.length) {
|
||||
return null;
|
||||
}
|
||||
@@ -132,7 +132,7 @@ const TableContent = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// 添加行按钮组件
|
||||
// Add line button component
|
||||
const AddRowButton = () => {
|
||||
const { isShowAddBtn } = useTableUI();
|
||||
const documentList = useKnowledgeStore(state => state.documentList);
|
||||
@@ -164,12 +164,12 @@ const AddRowButton = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// 主组件
|
||||
// main component
|
||||
export const TableDataView = () => {
|
||||
const { sliceListData } = useTableData();
|
||||
const slices = sliceListData?.list;
|
||||
|
||||
// 如果没有数据,只显示添加按钮
|
||||
// If there is no data, only the add button is displayed.
|
||||
if (!slices?.length) {
|
||||
return <AddRowButton />;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { createContext, useContext } from 'react';
|
||||
|
||||
import { type DatasetDataScrollList } from '@/service';
|
||||
|
||||
// 表格操作相关的 Context
|
||||
// Context related to table manipulation
|
||||
interface TableActionsContextType {
|
||||
setCurIndex: (index: number | ((prev: number) => number)) => void;
|
||||
setCurSliceId: (id: string | ((prev: string) => string)) => void;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { createContext, useContext } from 'react';
|
||||
|
||||
import { type DatasetDataScrollList } from '@/service';
|
||||
|
||||
// 表格数据相关的 Context
|
||||
// Context related to tabular data
|
||||
interface TableDataContextType {
|
||||
sliceListData: DatasetDataScrollList;
|
||||
curIndex: number;
|
||||
|
||||
@@ -19,7 +19,7 @@ import { type MutableRefObject } from 'react';
|
||||
|
||||
import { type TableViewMethods } from '@coze-common/table-view';
|
||||
|
||||
// 表格 UI 相关的 Context
|
||||
// Context related to table UI
|
||||
interface TableUIContextType {
|
||||
tableViewRef: MutableRefObject<TableViewMethods | null>;
|
||||
isLoadingMoreSliceList: boolean;
|
||||
|
||||
@@ -30,7 +30,7 @@ export const useCreateSlice = () => {
|
||||
const documentList = useKnowledgeStore(state => state.documentList);
|
||||
const curDoc = documentList?.[0];
|
||||
|
||||
// 创建切片
|
||||
// Create slice
|
||||
const { createSlice } = useCreateSliceService({
|
||||
onReload: (createItem: ISliceInfo) => {
|
||||
const list =
|
||||
|
||||
@@ -25,7 +25,7 @@ import { useScrollListSliceReq } from '@/service';
|
||||
export const useGetSliceListData = () => {
|
||||
const documentList = useKnowledgeStore(state => state.documentList);
|
||||
const curDocId = documentList?.[0]?.document_id;
|
||||
// 加载数据
|
||||
// load data
|
||||
const {
|
||||
data: sliceListData,
|
||||
mutate: mutateSliceListData,
|
||||
|
||||
@@ -20,7 +20,7 @@ const ADD_BTN_HEIGHT = 56;
|
||||
|
||||
export const useScroll = () => {
|
||||
const { sliceListData } = useTableData();
|
||||
// 滚动表格到底部
|
||||
// Scroll table to the bottom
|
||||
const scrollTableBodyToBottom = () => {
|
||||
const bodyDom = document.querySelector(
|
||||
'.table-view-box .semi-table-container>.semi-table-body',
|
||||
|
||||
@@ -24,7 +24,7 @@ export const useTableHeight = () => {
|
||||
const { sliceListData } = useTableData();
|
||||
const [tableH, setTableHeight] = useState<number | string>(0);
|
||||
|
||||
// 更新表格高度
|
||||
// Update table height
|
||||
useEffect(() => {
|
||||
const h = tableViewRef?.current?.getTableHeight();
|
||||
if (h) {
|
||||
|
||||
@@ -35,7 +35,7 @@ export const useAddRow = ({
|
||||
const { sliceListData } = useTableData();
|
||||
const { mutateSliceListData } = useTableActions();
|
||||
const handleAddRow = () => {
|
||||
/** 先增加容器的高度 */
|
||||
/** Increase the height of the container first */
|
||||
increaseTableHeight(ADD_BTN_HEIGHT);
|
||||
const items = JSON.parse(sliceListData?.list[0]?.content ?? '[]');
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@ import { useTableData } from '../../context/table-data-context';
|
||||
import { useTableActions } from '../../context/table-actions-context';
|
||||
|
||||
export const useDeleteSliceModal = () => {
|
||||
// 外部数据
|
||||
// external data
|
||||
const dataSetDetail = useKnowledgeStore(state => state.dataSetDetail);
|
||||
const setDataSetDetail = useKnowledgeStore(state => state.setDataSetDetail);
|
||||
const documentList = useKnowledgeStore(state => state.documentList);
|
||||
|
||||
// 内部数据
|
||||
// internal data
|
||||
const { sliceListData, delSliceIds } = useTableData();
|
||||
const { tableViewRef } = useTableUI();
|
||||
const { mutateSliceListData } = useTableActions();
|
||||
@@ -41,7 +41,7 @@ export const useDeleteSliceModal = () => {
|
||||
const curDoc = documentList?.[0];
|
||||
const slices = sliceListData?.list;
|
||||
|
||||
// 删除切片弹窗
|
||||
// Delete slice pop-up
|
||||
const { node: deleteSliceModalNode, delete: openDeleteSliceModal } =
|
||||
useSliceDeleteModal({
|
||||
onDel: async () => {
|
||||
|
||||
@@ -33,7 +33,7 @@ export const useTableSegmentModal = () => {
|
||||
const { mutateSliceListData } = useTableActions();
|
||||
const curDoc = documentList?.[0];
|
||||
|
||||
// 表格分段弹窗
|
||||
// table segmentation pop-up
|
||||
const {
|
||||
node: tableSegmentModalNode,
|
||||
edit: openTableSegmentModal,
|
||||
|
||||
@@ -48,11 +48,11 @@ export const useTableSliceOperations = ({
|
||||
return;
|
||||
}
|
||||
|
||||
/** 新增的行 */
|
||||
/** new row */
|
||||
const addIndex = indexs.filter(i => !slices[i].slice_id);
|
||||
const addIds = addIndex.map(i => slices[i]?.addId);
|
||||
const oldIndex = indexs.filter(v => !addIndex.includes(v));
|
||||
// 确保过滤掉undefined值
|
||||
// Make sure to filter out undefined values
|
||||
const sliceIds = oldIndex
|
||||
.map(i => slices[i].slice_id)
|
||||
.filter(Boolean) as string[];
|
||||
@@ -129,12 +129,12 @@ export const useTableSliceOperations = ({
|
||||
if (sliceId) {
|
||||
await updateSlice(sliceId as string, updateContent, updateValue);
|
||||
} else {
|
||||
/** 新增分片 */
|
||||
/** Add sharding */
|
||||
const createParams = await handleCreateSlice(updateContent);
|
||||
if (createParams && createSlice && curDoc?.document_id) {
|
||||
// 调用传入的createSlice方法创建新的分片
|
||||
// Call the createSlice method passed in to create a new sharding.
|
||||
try {
|
||||
// 这里需要使用props中传入的createSlice方法来调用API
|
||||
// Here you need to use the createSlice method passed in the props to call the API.
|
||||
await createSlice({
|
||||
document_id: curDoc.document_id,
|
||||
raw_text: updateContent,
|
||||
@@ -145,7 +145,7 @@ export const useTableSliceOperations = ({
|
||||
}
|
||||
}
|
||||
|
||||
// 改为接口请求成功后才更新
|
||||
// Change to update after the interface request is successful
|
||||
if (slices) {
|
||||
return true;
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export const useTableSliceOperations = ({
|
||||
}
|
||||
};
|
||||
|
||||
/** 弹窗编辑切片 */
|
||||
/** Pop-up editing slice */
|
||||
const handleModalEditSlice = (_record: TableViewRecord, index: number) => {
|
||||
if (!slices || index < 0 || index >= slices.length) {
|
||||
return;
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
|
||||
import { TableKnowledgeWorkspace } from './components/main';
|
||||
|
||||
// 导出组件
|
||||
// export component
|
||||
export { TableKnowledgeWorkspace };
|
||||
export type { TableKnowledgeWorkspaceProps } from './components/main';
|
||||
|
||||
@@ -66,7 +66,7 @@ const getTableCacheWidthMap = (tableKey: string) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* slice 数据转换为 TableView 组件接收的数据类型
|
||||
* Slice data to the data type received by the TableView component
|
||||
*/
|
||||
|
||||
export const getTableRenderColumnsData = ({
|
||||
@@ -158,7 +158,7 @@ export const getTableRenderColumnsData = ({
|
||||
/>
|
||||
);
|
||||
}
|
||||
// 针对违规内容高亮处理
|
||||
// Highlighting violations
|
||||
const isAudiFailed = record?.status === SliceStatus.AuditFailed;
|
||||
const textRender = () => (
|
||||
<div className={`w-full ${isAudiFailed ? 'text-red-500' : ''}`}>
|
||||
|
||||
@@ -75,7 +75,7 @@ export const DocSelector = ({
|
||||
}
|
||||
try {
|
||||
const regx = new RegExp(searchValue);
|
||||
// 搜索结果不展示「全部内容」选项
|
||||
// Search results do not show the "All Content" option
|
||||
return (
|
||||
(op.value !== 'all' && op.value === value) ||
|
||||
(op?.text as string)?.match(regx)
|
||||
|
||||
@@ -48,7 +48,7 @@ export const LevelContent: React.FC<LevelContentProps> = ({
|
||||
const canEdit = useKnowledgeStore(state => state.canEdit);
|
||||
const searchValue = useKnowledgeStore(state => state.searchValue);
|
||||
|
||||
// 转换层级分段数据为编辑器可用格式
|
||||
// Convert hierarchical segmented data into an editor-usable format
|
||||
const renderLevelSegmentsData = levelSegments.map(item =>
|
||||
createLevelDocumentChunkByLevelSegment(item),
|
||||
);
|
||||
|
||||
@@ -76,10 +76,10 @@ export const TextKnowledgeWorkspace = ({
|
||||
const documentList = useKnowledgeStore(state => state.documentList);
|
||||
const resourceNavigate = useDataNavigate();
|
||||
|
||||
// 初始化选择第一个文档
|
||||
// Initialize to select the first document
|
||||
useInitSelectFirstDoc();
|
||||
|
||||
// 文档管理
|
||||
// Document Management
|
||||
const {
|
||||
handleSelectDocument,
|
||||
handleRenameDocument,
|
||||
@@ -89,15 +89,15 @@ export const TextKnowledgeWorkspace = ({
|
||||
reloadDataset,
|
||||
});
|
||||
|
||||
// 文档基本信息
|
||||
// Documentation basic information
|
||||
const { curDoc, curDocId, isProcessing, processFinished, datasetId } =
|
||||
useDocumentInfo(progressMap);
|
||||
|
||||
// 文件预览
|
||||
// file preview
|
||||
const { showOriginalFile, handleToggleOriginalFile } =
|
||||
useFilePreview(curDocId);
|
||||
|
||||
// 文档片段数据
|
||||
// document fragment data
|
||||
const { loading, renderData, handleContentChange, reload } = useSliceData({
|
||||
curDocId,
|
||||
datasetId,
|
||||
@@ -107,7 +107,7 @@ export const TextKnowledgeWorkspace = ({
|
||||
rollbackDocumentSelection,
|
||||
});
|
||||
|
||||
// 层级分段数据
|
||||
// hierarchical segmented data
|
||||
const {
|
||||
levelSegments,
|
||||
selectionIDs,
|
||||
@@ -119,11 +119,11 @@ export const TextKnowledgeWorkspace = ({
|
||||
curDoc,
|
||||
});
|
||||
|
||||
// 片段计数器
|
||||
// fragment counter
|
||||
const { handleIncreaseSliceCount, handleDecreaseSliceCount } =
|
||||
useSliceCounter();
|
||||
|
||||
// 模态框
|
||||
// modal box
|
||||
const {
|
||||
deleteModalNode,
|
||||
showDeleteModal,
|
||||
@@ -153,10 +153,10 @@ export const TextKnowledgeWorkspace = ({
|
||||
},
|
||||
});
|
||||
|
||||
// 文档选项
|
||||
// Document Options
|
||||
const docOptions = getDocumentOptions(documentList, progressMap);
|
||||
|
||||
// 处理重新分段
|
||||
// Handle re-segmentation
|
||||
const handleResegment = () => {
|
||||
const isLocalText = Boolean(
|
||||
curDoc?.source_type === DocumentSource.Document,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user