feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
63
frontend/packages/common/chat-area/utils/README.md
Normal file
63
frontend/packages/common/chat-area/utils/README.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# @coze-common/chat-area-utils
|
||||
|
||||
utils in vanilla ts, no react, no logger, no I18n
|
||||
|
||||
## Overview
|
||||
|
||||
This package is part of the Coze Studio monorepo and provides utilities functionality. It serves as a core component in the Coze ecosystem.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
Add this package to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@coze-common/chat-area-utils": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
rush update
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```typescript
|
||||
import { /* exported functions/components */ } from '@coze-common/chat-area-utils';
|
||||
|
||||
// Example usage
|
||||
// TODO: Add specific usage examples
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Core functionality for Coze Studio
|
||||
- TypeScript support
|
||||
- Modern ES modules
|
||||
|
||||
## API Reference
|
||||
|
||||
Please refer to the TypeScript definitions for detailed API documentation.
|
||||
|
||||
## Development
|
||||
|
||||
This package is built with:
|
||||
|
||||
- TypeScript
|
||||
- Modern JavaScript
|
||||
- Vitest for testing
|
||||
- ESLint for code quality
|
||||
|
||||
## Contributing
|
||||
|
||||
This package is part of the Coze Studio monorepo. Please follow the monorepo contribution guidelines.
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 { describe, expect, vi } from 'vitest';
|
||||
|
||||
import { Deferred, sleep } from '../src/async';
|
||||
|
||||
it('sleep', async () => {
|
||||
vi.useFakeTimers();
|
||||
let count = 0;
|
||||
sleep(1000).then(() => (count = 1));
|
||||
vi.runAllTimers();
|
||||
expect(count).toBe(0);
|
||||
await Promise.resolve();
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
describe('test deferred', () => {
|
||||
it('works', async () => {
|
||||
const deferred = new Deferred<number>();
|
||||
deferred.resolve(1);
|
||||
expect(await deferred.promise).toBe(1);
|
||||
});
|
||||
|
||||
it('reject', async () => {
|
||||
const deferred = new Deferred();
|
||||
deferred.reject(123);
|
||||
try {
|
||||
await deferred.promise;
|
||||
} catch (err) {
|
||||
expect(err).toBe(123);
|
||||
}
|
||||
});
|
||||
|
||||
it('perform like promise', async () => {
|
||||
const deferred = new Deferred<string>();
|
||||
deferred.resolve('1');
|
||||
expect(await deferred).toBe('1');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
exhaustiveCheckSimple,
|
||||
exhaustiveCheckForRecord,
|
||||
} from '../src/exhaustive-check';
|
||||
|
||||
it('works', () => {
|
||||
const obj = { a: 1 };
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars -- .
|
||||
const { a, ...rest } = obj;
|
||||
exhaustiveCheckForRecord(rest);
|
||||
type N = 1;
|
||||
const n: N = 1;
|
||||
switch (n) {
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
exhaustiveCheckSimple(n);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 { expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
sortInt64CompareFn,
|
||||
getIsDiffWithinRange,
|
||||
getMinMax,
|
||||
compareInt64,
|
||||
getInt64AbsDifference,
|
||||
} from '../src/int64';
|
||||
|
||||
it('正确排序', () => {
|
||||
expect(['123', '3', '02', '01234'].sort(sortInt64CompareFn)).toMatchObject([
|
||||
'02',
|
||||
'3',
|
||||
'123',
|
||||
'01234',
|
||||
]);
|
||||
});
|
||||
|
||||
it('计算两个数字差值小于范围', () => {
|
||||
expect(getIsDiffWithinRange('1234567', '12345678923456745678', 50)).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
getIsDiffWithinRange('12345678923456745679', '12345678923456745678', 50),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('get min max', () => {
|
||||
expect(getMinMax('1', '3', '2', '5')).toMatchObject({ min: '1', max: '5' });
|
||||
expect(getMinMax('3', '2', '5')).toMatchObject({ min: '2', max: '5' });
|
||||
expect(getMinMax('3', '2', '1')).toMatchObject({ min: '1', max: '3' });
|
||||
expect(getMinMax('3')).toMatchObject({ min: '3', max: '3' });
|
||||
expect(getMinMax()).toBeNull();
|
||||
});
|
||||
|
||||
it('compare right', () => {
|
||||
expect(compareInt64('1').greaterThan('0')).toBe(true);
|
||||
expect(compareInt64('1').lesserThan('10')).toBe(true);
|
||||
expect(compareInt64('1').eq('1')).toBe(true);
|
||||
});
|
||||
|
||||
it('get diff right', () => {
|
||||
expect(getInt64AbsDifference('10', '200')).toBe(190);
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 { expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
typeSafeJsonParse,
|
||||
typeSafeJsonParseEnhanced,
|
||||
} from '../src/json-parse';
|
||||
|
||||
it('simple, parse json', () => {
|
||||
const items = [1, true, [], { a: 1 }];
|
||||
const empty = () => undefined;
|
||||
expect(typeSafeJsonParse(JSON.stringify(items[0]), empty)).toBe(1);
|
||||
expect(typeSafeJsonParse(JSON.stringify(items[1]), empty)).toBe(true);
|
||||
expect(typeSafeJsonParse(JSON.stringify(items[2]), empty)).toMatchObject(
|
||||
items[2],
|
||||
);
|
||||
expect(typeSafeJsonParse(JSON.stringify(items[3]), empty)).toMatchObject(
|
||||
items[3],
|
||||
);
|
||||
});
|
||||
|
||||
it('simple, trigger error', () => {
|
||||
const onErr = vi.fn();
|
||||
expect(typeSafeJsonParse('a', onErr)).toBeNull();
|
||||
expect(onErr.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('enhanced, parse json', () => {
|
||||
const items = [1, true, [], { a: 1 }];
|
||||
const getEmpty = () => ({
|
||||
onVerifyError: () => undefined,
|
||||
onParseError: () => undefined,
|
||||
verifyStruct: (sth: unknown): sth is unknown => true,
|
||||
});
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: JSON.stringify(items[0]),
|
||||
...getEmpty(),
|
||||
}),
|
||||
).toBe(1);
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: JSON.stringify(items[1]),
|
||||
...getEmpty(),
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: JSON.stringify(items[2]),
|
||||
...getEmpty(),
|
||||
}),
|
||||
).toMatchObject(items[2]);
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: JSON.stringify(items[3]),
|
||||
...getEmpty(),
|
||||
}),
|
||||
).toMatchObject(items[3]);
|
||||
});
|
||||
|
||||
it('enhanced, trigger parse error', () => {
|
||||
const onParseError = vi.fn();
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: 'ax',
|
||||
onParseError,
|
||||
onVerifyError: () => undefined,
|
||||
verifyStruct: (sth): sth is unknown => true,
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(onParseError.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('enhanced, catch verify not pass error', () => {
|
||||
const onVerifyError = vi.fn();
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: 'ax',
|
||||
onParseError: () => undefined,
|
||||
onVerifyError,
|
||||
verifyStruct: (sth): sth is unknown => false,
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(onVerifyError.mock.calls.length).toBe(1);
|
||||
expect(onVerifyError.mock.calls[0][0]).toMatchObject({
|
||||
message: 'verify struct no pass',
|
||||
});
|
||||
});
|
||||
|
||||
it('enhanced, catch verify broken', () => {
|
||||
const onVerifyError = vi.fn();
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: 'ax',
|
||||
onParseError: () => undefined,
|
||||
onVerifyError,
|
||||
verifyStruct: (sth): sth is unknown => {
|
||||
const obj = Object(null);
|
||||
return 'x' in obj.x;
|
||||
},
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(onVerifyError.mock.calls.length).toBe(1);
|
||||
expect(onVerifyError.mock.calls[0][0]).toBeInstanceOf(TypeError);
|
||||
expect(onVerifyError.mock.calls[0][0].message).toEqual(
|
||||
expect.stringContaining("Cannot use 'in' operator"),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 { expect, it } from 'vitest';
|
||||
|
||||
import { performSimpleObjectTypeCheck } from '../src/perform-simple-type-check';
|
||||
|
||||
it('check simple obj', () => {
|
||||
expect(
|
||||
performSimpleObjectTypeCheck(
|
||||
{
|
||||
a: 1,
|
||||
b: '2',
|
||||
},
|
||||
[
|
||||
['a', 'is-number'],
|
||||
['b', 'is-string'],
|
||||
],
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('not block', () => {
|
||||
expect(performSimpleObjectTypeCheck([], [])).toBe(true);
|
||||
expect(
|
||||
performSimpleObjectTypeCheck(
|
||||
{
|
||||
a: 1,
|
||||
},
|
||||
[],
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
performSimpleObjectTypeCheck(
|
||||
{
|
||||
a: 1,
|
||||
b: '2',
|
||||
},
|
||||
[['a', 'is-string']],
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('only check object', () => {
|
||||
expect(performSimpleObjectTypeCheck(1, [])).toBe(false);
|
||||
expect(performSimpleObjectTypeCheck('1', [])).toBe(false);
|
||||
expect(performSimpleObjectTypeCheck(null, [])).toBe(false);
|
||||
expect(performSimpleObjectTypeCheck(undefined, [])).toBe(false);
|
||||
});
|
||||
|
||||
it('check key exists', () => {
|
||||
expect(performSimpleObjectTypeCheck({}, [['a', 'is-string']])).toBe(false);
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 { expect, it, vi } from 'vitest';
|
||||
|
||||
import { RateLimit } from '../src/rate-limit';
|
||||
|
||||
it('limit rate', async () => {
|
||||
vi.useFakeTimers();
|
||||
const request = vi.fn();
|
||||
const limiter = new RateLimit(request, {
|
||||
limit: 3,
|
||||
onLimitDelay: 1000,
|
||||
timeWindow: 5000,
|
||||
});
|
||||
for (const i of [1, 2, 3, 4, 5]) {
|
||||
limiter.invoke(i);
|
||||
}
|
||||
expect(request.mock.calls.length).toBe(3);
|
||||
// 1000
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(request.mock.calls.length).toBe(4);
|
||||
// 2000
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(request.mock.calls.length).toBe(5);
|
||||
// 3000
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
limiter.invoke();
|
||||
limiter.invoke();
|
||||
// 3010
|
||||
await vi.advanceTimersByTimeAsync(10);
|
||||
expect(request.mock.calls.length).toBe(6);
|
||||
// 4010
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(request.mock.calls.length).toBe(7);
|
||||
|
||||
// 离开窗口
|
||||
await vi.advanceTimersByTimeAsync(5000);
|
||||
limiter.invoke();
|
||||
limiter.invoke();
|
||||
limiter.invoke();
|
||||
expect(request.mock.calls.length).toBe(10);
|
||||
// 进入限流
|
||||
limiter.invoke();
|
||||
await vi.advanceTimersByTimeAsync(100);
|
||||
expect(request.mock.calls.length).toBe(10);
|
||||
expect((limiter as unknown as { records: number[] }).records.length).toBe(4);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 { expect, it, vi } from 'vitest';
|
||||
|
||||
import { safeAsyncThrow } from '../src/safe-async-throw';
|
||||
|
||||
it('throw in IS_DEV_MODE', () => {
|
||||
vi.stubGlobal('IS_PROD', true);
|
||||
vi.stubGlobal('IS_DEV_MODE', true);
|
||||
expect(() => safeAsyncThrow('1')).toThrow();
|
||||
});
|
||||
|
||||
it('do not throw in BUILD env', () => {
|
||||
vi.stubGlobal('IS_DEV_MODE', false);
|
||||
vi.stubGlobal('IS_BOE', false);
|
||||
vi.stubGlobal('IS_PROD', true);
|
||||
vi.stubGlobal('window', {
|
||||
gfdatav1: {
|
||||
canary: 0,
|
||||
},
|
||||
});
|
||||
vi.useFakeTimers();
|
||||
safeAsyncThrow('1');
|
||||
try {
|
||||
vi.runAllTimers();
|
||||
} catch (e) {
|
||||
expect((e as Error).message).toBe('[chat-area] 1');
|
||||
}
|
||||
vi.useRealTimers();
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 { expect, it, vi } from 'vitest';
|
||||
|
||||
import { updateOnlyDefined } from '../src/update-only-defined';
|
||||
|
||||
it('update only defined', () => {
|
||||
const updater = vi.fn();
|
||||
updateOnlyDefined(updater, {
|
||||
a: undefined,
|
||||
b: 1,
|
||||
});
|
||||
expect(updater.mock.calls[0][0]).toMatchObject({
|
||||
b: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('do not run updater if item value is only undefined', () => {
|
||||
const updater = vi.fn();
|
||||
updateOnlyDefined(updater, {
|
||||
a: undefined,
|
||||
b: undefined,
|
||||
});
|
||||
expect(updater.mock.calls.length).toBe(0);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"codecov": {
|
||||
"coverage": 0,
|
||||
"incrementCoverage": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
47
frontend/packages/common/chat-area/utils/index.ts
Normal file
47
frontend/packages/common/chat-area/utils/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 { performSimpleObjectTypeCheck } from './src/perform-simple-type-check';
|
||||
export { typeSafeJsonParse, typeSafeJsonParseEnhanced } from './src/json-parse';
|
||||
export { getReportError } from './src/get-report-error';
|
||||
export { safeAsyncThrow } from './src/safe-async-throw';
|
||||
export { updateOnlyDefined } from './src/update-only-defined';
|
||||
export {
|
||||
sortInt64CompareFn,
|
||||
getIsDiffWithinRange,
|
||||
getInt64AbsDifference,
|
||||
compareInt64,
|
||||
getMinMax,
|
||||
compute,
|
||||
} from './src/int64';
|
||||
|
||||
export { type MakeValueUndefinable } from './src/type-helper';
|
||||
export { sleep, Deferred } from './src/async';
|
||||
export { flatMapByKeyList } from './src/collection';
|
||||
export {
|
||||
exhaustiveCheckForRecord,
|
||||
exhaustiveCheckSimple,
|
||||
} from './src/exhaustive-check';
|
||||
export { RateLimit } from './src/rate-limit';
|
||||
export { parseMarkdownHelper } from './src/parse-markdown/parse-markdown-to-text';
|
||||
export {
|
||||
type Root,
|
||||
type Link,
|
||||
type Image,
|
||||
type Text,
|
||||
type RootContent,
|
||||
type Parent,
|
||||
} from 'mdast';
|
||||
38
frontend/packages/common/chat-area/utils/package.json
Normal file
38
frontend/packages/common/chat-area/utils/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@coze-common/chat-area-utils",
|
||||
"version": "0.0.1",
|
||||
"description": "utils in vanilla ts, no react, no logger, no I18n",
|
||||
"license": "Apache-2.0",
|
||||
"author": "wanglitong@bytedance.com",
|
||||
"maintainers": [],
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coze-arch/bot-env": "workspace:*",
|
||||
"mdast": "3.0.0-alpha.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-md-box-adapter": "workspace:*",
|
||||
"@coze-arch/bot-typings": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@types/lodash-es": "^4.17.10",
|
||||
"@types/mdast": "4.0.3",
|
||||
"@types/node": "^18",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"big-integer": "^1.6.52",
|
||||
"lodash-es": "^4.17.21",
|
||||
"sucrase": "^3.32.0",
|
||||
"vitest": "~3.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"lodash-es": "^4.17.21"
|
||||
}
|
||||
}
|
||||
|
||||
32
frontend/packages/common/chat-area/utils/src/async.ts
Normal file
32
frontend/packages/common/chat-area/utils/src/async.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const sleep = (t = 0) => new Promise(resolve => setTimeout(resolve, t));
|
||||
|
||||
export class Deferred<T = void> {
|
||||
promise: Promise<T>;
|
||||
resolve!: (value: T) => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- .
|
||||
reject!: (reason?: any) => void;
|
||||
then: Promise<T>['then'];
|
||||
constructor() {
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
this.then = this.promise.then.bind(this.promise);
|
||||
}
|
||||
}
|
||||
33
frontend/packages/common/chat-area/utils/src/collection.ts
Normal file
33
frontend/packages/common/chat-area/utils/src/collection.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { safeAsyncThrow } from './safe-async-throw';
|
||||
|
||||
export const flatMapByKeyList = <T>(
|
||||
map: Map<string, T>,
|
||||
arr: string[],
|
||||
): T[] => {
|
||||
const res: T[] = [];
|
||||
for (const key of arr) {
|
||||
const val = map.get(key);
|
||||
if (!val) {
|
||||
safeAsyncThrow(`[flatMapByKeyList] cannot find ${key} in map`);
|
||||
continue;
|
||||
}
|
||||
res.push(val);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 检查没有遗漏的项
|
||||
*/
|
||||
export const exhaustiveCheckForRecord = (_: Record<string, never>) => undefined;
|
||||
|
||||
export const exhaustiveCheckSimple = (_: never) => undefined;
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isObject } from 'lodash-es';
|
||||
|
||||
/**
|
||||
* @param inputError 传啥都行,一般是 catch (e) 那个 e
|
||||
* @param reason 多余的解释,我感觉有 eventName 了没啥用
|
||||
*/
|
||||
export const getReportError = (
|
||||
inputError: unknown,
|
||||
reason?: string,
|
||||
): {
|
||||
error: Error;
|
||||
meta: Record<string, unknown>;
|
||||
} => {
|
||||
if (inputError instanceof Error) {
|
||||
return {
|
||||
error: inputError,
|
||||
meta: { reason },
|
||||
};
|
||||
}
|
||||
if (!isObject(inputError)) {
|
||||
return {
|
||||
error: new Error(String(inputError)),
|
||||
meta: { reason },
|
||||
};
|
||||
}
|
||||
return {
|
||||
error: new Error(''),
|
||||
meta: { ...covertInputObject(inputError), reason },
|
||||
};
|
||||
};
|
||||
|
||||
const covertInputObject = (inputError: object) => {
|
||||
if ('reason' in inputError) {
|
||||
return {
|
||||
...inputError,
|
||||
reasonOfInputError: inputError.reason,
|
||||
};
|
||||
}
|
||||
return inputError;
|
||||
};
|
||||
70
frontend/packages/common/chat-area/utils/src/int64.ts
Normal file
70
frontend/packages/common/chat-area/utils/src/int64.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 bigInt, { max, min } from 'big-integer';
|
||||
|
||||
export const sortInt64CompareFn = (a: string, b: string) =>
|
||||
bigInt(a).compare(b);
|
||||
|
||||
/** O(1) 遍历 */
|
||||
export const getMinMax = (...nums: string[]) => {
|
||||
const num = nums.at(0);
|
||||
if (num === undefined) {
|
||||
return null;
|
||||
}
|
||||
let minRes = bigInt(num);
|
||||
let maxRes = bigInt(num);
|
||||
for (const curStr of nums) {
|
||||
const cur = bigInt(curStr);
|
||||
minRes = min(minRes, cur);
|
||||
maxRes = max(maxRes, cur);
|
||||
}
|
||||
return {
|
||||
min: minRes.toString(),
|
||||
max: maxRes.toString(),
|
||||
};
|
||||
};
|
||||
|
||||
export const getIsDiffWithinRange = (a: string, b: string, range: number) => {
|
||||
const diff = bigInt(a).minus(bigInt(b));
|
||||
const abs = diff.abs();
|
||||
return abs.lesser(bigInt(range));
|
||||
};
|
||||
|
||||
export const getInt64AbsDifference = (a: string, b: string) => {
|
||||
const diff = bigInt(a).minus(bigInt(b));
|
||||
const abs = diff.abs();
|
||||
return abs.toJSNumber();
|
||||
};
|
||||
|
||||
export const compareInt64 = (a: string) => {
|
||||
const bigA = bigInt(a);
|
||||
return {
|
||||
greaterThan: (b: string) => bigA.greater(bigInt(b)),
|
||||
lesserThan: (b: string) => bigA.lesser(bigInt(b)),
|
||||
eq: (b: string) => bigA.eq(bigInt(b)),
|
||||
};
|
||||
};
|
||||
|
||||
export const compute = (a: string) => {
|
||||
const bigA = bigInt(a);
|
||||
return {
|
||||
add: (b: string) => bigA.add(b).toString(),
|
||||
subtract: (b: string) => bigA.subtract(b).toString(),
|
||||
prev: () => bigA.prev().toString(),
|
||||
next: () => bigA.next().toString(),
|
||||
};
|
||||
};
|
||||
66
frontend/packages/common/chat-area/utils/src/json-parse.ts
Normal file
66
frontend/packages/common/chat-area/utils/src/json-parse.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const typeSafeJsonParse = (
|
||||
str: string,
|
||||
onParseError: (error: Error) => void,
|
||||
): unknown => {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
onParseError(e as Error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 泛型类型标注可能需要使用 type 声明,
|
||||
* refer: https://github.com/microsoft/TypeScript/issues/15300.
|
||||
*/
|
||||
export const typeSafeJsonParseEnhanced = <T>({
|
||||
str,
|
||||
onParseError,
|
||||
verifyStruct,
|
||||
onVerifyError,
|
||||
}: {
|
||||
str: string;
|
||||
onParseError: (error: Error) => void;
|
||||
/**
|
||||
* 实现一个类型校验,返回是否通过(boolean);实际上还是靠自觉.
|
||||
* 可以单独定义, 也可以写作内联 function, 但是注意返回值标注为 predicate,
|
||||
* refer: https://github.com/microsoft/TypeScript/issues/38390.
|
||||
*/
|
||||
verifyStruct: (sth: unknown) => sth is T;
|
||||
/** 错误原因: 校验崩溃; 校验未通过 */
|
||||
onVerifyError: (error: Error) => void;
|
||||
}): T | null => {
|
||||
const res = typeSafeJsonParse(str, onParseError);
|
||||
|
||||
function assertStruct(resLocal: unknown): asserts resLocal is T {
|
||||
const ok = verifyStruct(resLocal);
|
||||
if (!ok) {
|
||||
throw new Error('verify struct no pass');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
assertStruct(res);
|
||||
return res;
|
||||
} catch (e) {
|
||||
onVerifyError(e as Error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type Text, type Link, type Parent, type Image } from 'mdast';
|
||||
import { isObject, isUndefined } from 'lodash-es';
|
||||
/**
|
||||
* 将markdown转为纯文本
|
||||
* @param markdown Markdown文本
|
||||
* @returns string 纯文本
|
||||
*/
|
||||
export const getTextFromAst = (ast: unknown): string => {
|
||||
if (isParent(ast)) {
|
||||
return `${ast.children.map(child => getTextFromAst(child)).join('')}`;
|
||||
}
|
||||
|
||||
if (isText(ast)) {
|
||||
return ast.value;
|
||||
}
|
||||
|
||||
if (isLink(ast)) {
|
||||
return `[${getTextFromAst(ast.children)}](${ast.url})`;
|
||||
}
|
||||
|
||||
if (isImage(ast)) {
|
||||
return ``;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const isParent = (ast: unknown): ast is Parent =>
|
||||
!!ast && isObject(ast) && 'children' in ast && !isUndefined(ast?.children);
|
||||
|
||||
const isLink = (ast: unknown): ast is Link =>
|
||||
isObject(ast) && 'type' in ast && !isUndefined(ast) && ast.type === 'link';
|
||||
|
||||
const isImage = (ast: unknown): ast is Image =>
|
||||
!isUndefined(ast) && isObject(ast) && 'type' in ast && ast.type === 'image';
|
||||
|
||||
const isText = (ast: unknown): ast is Text =>
|
||||
!isUndefined(ast) && isObject(ast) && 'type' in ast && ast.type === 'text';
|
||||
|
||||
export const parseMarkdownHelper = {
|
||||
isParent,
|
||||
isLink,
|
||||
isImage,
|
||||
isText,
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 { isNumber, isObject, isString } from 'lodash-es';
|
||||
|
||||
type CheckMethodName = 'is-string' | 'is-number';
|
||||
|
||||
const checkMethodsMap = new Map<CheckMethodName, (sth: unknown) => boolean>([
|
||||
['is-string', isString],
|
||||
['is-number', isNumber],
|
||||
]);
|
||||
|
||||
/**
|
||||
* think about:
|
||||
* https://www.npmjs.com/package/type-plus
|
||||
* https://www.npmjs.com/package/generic-type-guard
|
||||
* https://github.com/runtypes/runtypes
|
||||
*/
|
||||
export const performSimpleObjectTypeCheck = <T extends Record<string, unknown>>(
|
||||
sth: unknown,
|
||||
pairs: [key: keyof T, checkMethod: CheckMethodName][],
|
||||
): sth is T => {
|
||||
if (!isObject(sth)) {
|
||||
return false;
|
||||
}
|
||||
return pairs.every(([k, type]) => {
|
||||
if (!(k in sth)) {
|
||||
return false;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- runtime safe
|
||||
// @ts-expect-error
|
||||
const val = sth[k];
|
||||
return checkMethodsMap.get(type)?.(val);
|
||||
});
|
||||
};
|
||||
86
frontend/packages/common/chat-area/utils/src/rate-limit.ts
Normal file
86
frontend/packages/common/chat-area/utils/src/rate-limit.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 { sleep } from './async';
|
||||
|
||||
type Fn<ARGS extends unknown[], Ret = unknown> = (...args: ARGS) => Ret;
|
||||
|
||||
/**
|
||||
* 限流器,对于被限流的异步方法进行以下形式的限流:
|
||||
* 1. 在 timeWindow 内的前 limit 个请求不做限制,立即发送
|
||||
* 2. timeWindow 内超过 limit 个请求后,对每个请求依次添加 onLimitDelay 毫秒的延迟
|
||||
*
|
||||
* 注意是排队添加,形如 invoke: [1(0ms), 2(0ms), 3(0ms), 4(0ms)]; limit: [1(0ms), 2(0ms), 3(100ms), 4(200ms)]
|
||||
*
|
||||
* 另注:这个设计遭到了猛烈抨击,认为 debounce 可以代替掉,实现过于复杂,但是考虑:
|
||||
* 1. 支持列表双向加载的拉取,简单使用 debounce 可能导致请求某侧丢失;添加延时可以保证不丢失请求
|
||||
* 2. 列表拉取一旦出现死循环,可能导致恶性问题,如密集地对服务端接口的高频访问
|
||||
*
|
||||
* 以上场景通常不应出现,所以 limit 设计也只是对极端场景的兜底,上层 UI 错误理应得到妥善解决
|
||||
* TODO: wlt - 补充 testcase
|
||||
*/
|
||||
export class RateLimit<ARGS extends unknown[], Ret> {
|
||||
constructor(
|
||||
private fn: Fn<ARGS, Promise<Ret>>,
|
||||
private config: {
|
||||
onLimitDelay: number;
|
||||
limit: number;
|
||||
timeWindow: number;
|
||||
},
|
||||
) {}
|
||||
|
||||
private records: number[] = [];
|
||||
|
||||
private getNewInvokeDelay(): number {
|
||||
const { timeWindow, limit, onLimitDelay } = this.config;
|
||||
const now = Date.now();
|
||||
const windowEdge = now - timeWindow;
|
||||
const idx = this.records.findIndex(t => t >= windowEdge);
|
||||
if (idx < 0) {
|
||||
return 0;
|
||||
}
|
||||
const lasts = this.records.slice(idx);
|
||||
if (lasts.length < limit) {
|
||||
return 0;
|
||||
}
|
||||
const last = lasts.at(-1);
|
||||
if (!last) {
|
||||
return 0;
|
||||
}
|
||||
return last + onLimitDelay - now;
|
||||
}
|
||||
|
||||
private clearRecords() {
|
||||
const { timeWindow } = this.config;
|
||||
const now = Date.now();
|
||||
const windowEdge = now - timeWindow;
|
||||
const idx = this.records.findLastIndex(t => t < windowEdge);
|
||||
if (idx >= 0) {
|
||||
this.records = this.records.slice(idx + 1);
|
||||
}
|
||||
}
|
||||
|
||||
invoke = async (...args: ARGS): Promise<Ret> => {
|
||||
const invokeDelay = this.getNewInvokeDelay();
|
||||
const now = Date.now();
|
||||
this.records.push(invokeDelay + now);
|
||||
if (invokeDelay) {
|
||||
await sleep(invokeDelay);
|
||||
}
|
||||
this.clearRecords();
|
||||
return this.fn(...args);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 非线上环境阻塞;构建后仅做异常输出和异步抛出错误
|
||||
*/
|
||||
export const safeAsyncThrow = (e: string) => {
|
||||
const err = new Error(`[chat-area] ${e}`);
|
||||
if (IS_DEV_MODE || IS_BOE) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
20
frontend/packages/common/chat-area/utils/src/type-helper.ts
Normal file
20
frontend/packages/common/chat-area/utils/src/type-helper.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- 不知道为啥 unknown 不行,会导致类型转换失败
|
||||
export type MakeValueUndefinable<T extends Record<string, any>> = {
|
||||
[k in keyof T]: T[k] | undefined;
|
||||
};
|
||||
17
frontend/packages/common/chat-area/utils/src/typings.d.ts
vendored
Normal file
17
frontend/packages/common/chat-area/utils/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' />
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 { isUndefined, omitBy } from 'lodash-es';
|
||||
|
||||
/**
|
||||
* zustand update 辅助方法,检查入参对象,丢弃 value 为 undefined 的项.
|
||||
* zustand 自身没有过滤逻辑,如果类型没有问题,可能意外地将项目置为 undefined 值
|
||||
*/
|
||||
export const updateOnlyDefined = <T extends Record<string, unknown>>(
|
||||
updater: (sth: T) => void,
|
||||
val: T,
|
||||
) => {
|
||||
const left = omitBy(val, isUndefined) as T;
|
||||
if (!Object.keys(left).length) {
|
||||
return;
|
||||
}
|
||||
updater(left);
|
||||
};
|
||||
32
frontend/packages/common/chat-area/utils/tsconfig.build.json
Normal file
32
frontend/packages/common/chat-area/utils/tsconfig.build.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"types": [],
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../arch/bot-env/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-md-box-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../arch/bot-typings/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/eslint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/ts-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../../config/vitest-config/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/common/chat-area/utils/tsconfig.json
Normal file
15
frontend/packages/common/chat-area/utils/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.misc.json"
|
||||
}
|
||||
],
|
||||
"exclude": ["**/*"]
|
||||
}
|
||||
17
frontend/packages/common/chat-area/utils/tsconfig.misc.json
Normal file
17
frontend/packages/common/chat-area/utils/tsconfig.misc.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"include": ["index.ts", "__tests__", "vitest.config.ts"],
|
||||
"exclude": ["./dist"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/globals"]
|
||||
}
|
||||
}
|
||||
22
frontend/packages/common/chat-area/utils/vitest.config.ts
Normal file
22
frontend/packages/common/chat-area/utils/vitest.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { defineConfig } from '@coze-arch/vitest-config';
|
||||
|
||||
export default defineConfig({
|
||||
dirname: __dirname,
|
||||
preset: 'web',
|
||||
});
|
||||
Reference in New Issue
Block a user