feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useRef } from 'react';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- .
|
||||
type Fn<ARGS extends any[], R> = (...args: ARGS) => R;
|
||||
|
||||
// https://github.com/Volune/use-event-callback/blob/master/src/index.ts
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- .
|
||||
export const useEventCallback = <A extends any[], R>(
|
||||
fn: Fn<A, R>,
|
||||
): Fn<A, R> => {
|
||||
const ref = useRef(fn);
|
||||
ref.current = fn;
|
||||
const exposedRef = useRef((...args: A) => ref.current(...args));
|
||||
return exposedRef.current;
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState, useRef, useLayoutEffect } from 'react';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- x
|
||||
type Destructor = (() => void) | void;
|
||||
type Fn<ARGS extends unknown[]> = (...args: ARGS) => Destructor;
|
||||
|
||||
export const useImperativeLayoutEffect = <Params extends unknown[]>(
|
||||
effect: Fn<Params>,
|
||||
) => {
|
||||
const [effectValue, setEffectValue] = useState(0);
|
||||
const paramRef = useRef<Params>();
|
||||
const effectRef = useRef<Fn<Params>>(() => undefined);
|
||||
effectRef.current = effect;
|
||||
useLayoutEffect(() => {
|
||||
if (!effectValue) {
|
||||
return;
|
||||
}
|
||||
// 经过一次运行后一定不为 undefined
|
||||
return paramRef.current && effectRef.current(...paramRef.current);
|
||||
}, [effectValue]);
|
||||
|
||||
return (...args: Params) => {
|
||||
paramRef.current = args;
|
||||
setEffectValue(pre => pre + 1);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useEventCallback } from './use-event-callback';
|
||||
|
||||
type SearchStage = 'empty' | 'debouncing' | 'searching' | 'failed' | 'success';
|
||||
|
||||
export interface UseSearchConfig<Payload, Res> {
|
||||
debounceInterval: number;
|
||||
adjustDebounce?: (payload: Payload | null) => number;
|
||||
onError?: (e: unknown) => void;
|
||||
onSuccess?: (searchRes: Res, payload: Payload) => void;
|
||||
}
|
||||
|
||||
// todo 补充关于 res 重置的测试 case
|
||||
/** 小心 service 引用变化! 一旦变化会触发重新加载 */
|
||||
export const useSearch = <Payload, Res>(
|
||||
service: (payload: Payload) => Promise<Res>,
|
||||
{
|
||||
onError,
|
||||
debounceInterval,
|
||||
adjustDebounce,
|
||||
onSuccess,
|
||||
}: UseSearchConfig<Payload, Res>,
|
||||
) => {
|
||||
const [payload, setPayload] = useState<Payload | null>(null);
|
||||
const [searchStage, setSearchStage] = useState<SearchStage>('empty');
|
||||
const [res, setRes] = useState<Res | null>(null);
|
||||
const [triggerId, setTriggerId] = useState(0);
|
||||
const debounceIdRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const isEmpty = (localPayload: Payload | null): localPayload is null =>
|
||||
localPayload === null;
|
||||
|
||||
const doSearch = useEventCallback(() => {
|
||||
clearTimeout(debounceIdRef.current);
|
||||
const finalDebounceTime = adjustDebounce?.(payload) ?? debounceInterval;
|
||||
debounceIdRef.current = setTimeout(async () => {
|
||||
setRes(null);
|
||||
const searchCount = debounceIdRef.current;
|
||||
if (isEmpty(payload)) {
|
||||
setSearchStage('empty');
|
||||
return;
|
||||
}
|
||||
setSearchStage('searching');
|
||||
try {
|
||||
const searchRes = await service(payload);
|
||||
if (searchCount !== debounceIdRef.current) {
|
||||
return;
|
||||
}
|
||||
setRes(searchRes);
|
||||
setSearchStage('success');
|
||||
onSuccess?.(searchRes, payload);
|
||||
} catch (e) {
|
||||
if (searchCount !== debounceIdRef.current) {
|
||||
return;
|
||||
}
|
||||
console.error('[doSearch in use-search]', e);
|
||||
onError?.(e);
|
||||
setSearchStage('failed');
|
||||
}
|
||||
}, finalDebounceTime);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setRes(null);
|
||||
if (isEmpty(payload)) {
|
||||
setSearchStage('empty');
|
||||
} else {
|
||||
setSearchStage('debouncing');
|
||||
}
|
||||
doSearch();
|
||||
}, [payload, service, triggerId]);
|
||||
return {
|
||||
/** 注意清空时设置为 null */
|
||||
setPayload,
|
||||
searchStage,
|
||||
res,
|
||||
/** 主要用于重试 */
|
||||
run: () => setTriggerId(c => c + 1),
|
||||
};
|
||||
};
|
||||
19
frontend/packages/common/chat-area/hooks/src/index.ts
Normal file
19
frontend/packages/common/chat-area/hooks/src/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { useImperativeLayoutEffect } from './hooks/use-imperative-layout-effect';
|
||||
export { useSearch } from './hooks/use-search';
|
||||
export { useEventCallback } from './hooks/use-event-callback';
|
||||
17
frontend/packages/common/chat-area/hooks/src/typings.d.ts
vendored
Normal file
17
frontend/packages/common/chat-area/hooks/src/typings.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
Reference in New Issue
Block a user