feat: manually mirror opencoze's code from bytedance

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

View File

@@ -0,0 +1,5 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
});

View File

@@ -0,0 +1,15 @@
# @coze-studio/open-auth
## Features
- [x] eslint & ts
- [x] esm bundle
- [x] umd bundle
- [x] storybook
## Commands
- init: `rush update`
- dev: `npm run dev`
- build: `npm run build`

View File

@@ -0,0 +1,357 @@
/*
* 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 { renderHook, act } from '@testing-library/react-hooks';
import {
useGetPATList,
useCreatePAT,
useUpdatePAT,
useDeletePAT,
usePATPermission,
} from '@/hooks/pat/use-token';
vi.mock('@coze-arch/bot-api', () => ({
patPermissionApi: {
ListPersonalAccessTokensByCreator: vi
.fn()
.mockRejectedValueOnce('error')
.mockResolvedValue({
code: 0,
msg: '',
data: {
personal_access_tokens: [
{
id: 'id',
name: 'name',
created_at: 0,
updated_at: 0,
last_used_at: 0,
expire_at: 0,
},
],
},
}),
ListPersonalAccessTokens: vi
.fn()
.mockRejectedValueOnce('error')
.mockRejectedValueOnce('error')
.mockResolvedValue({
code: 0,
data: {
personal_access_tokens: [
{
id: '2124658667281',
name: 'Secret token--480-c',
created_at: 1711697638,
updated_at: 1711697638,
last_used_at: -1,
expire_at: 1711699200,
},
],
},
msg: '',
}),
CreatePersonalAccessTokenAndPermission: vi
.fn()
.mockRejectedValueOnce('error')
.mockResolvedValue({
code: 0,
data: {
token: 'pat',
personal_access_token: {
id: '2106243199900',
name: 'Secret token',
created_at: 1712668504,
updated_at: 1712668504,
last_used_at: -1,
expire_at: 1712754904,
},
},
msg: '',
}),
UpdatePersonalAccessTokenAndPermission: vi
.fn()
.mockRejectedValueOnce('error')
.mockResolvedValue({
code: 0,
data: {},
msg: '',
}),
DeletePersonalAccessTokenAndPermission: vi
.fn()
.mockRejectedValueOnce('error')
.mockResolvedValue({
code: 0,
data: {},
msg: '',
}),
GetPersonalAccessTokenAndPermission: vi
.fn()
.mockRejectedValueOnce('error')
.mockResolvedValue({
code: 0,
data: {
personal_access_token: {
id: '2196515701286',
name: 'Secret token',
created_at: 1711946476,
updated_at: 1711946476,
last_used_at: -1,
expire_at: -1,
},
workspace_permission: {
option: 2,
permission_list: [
{
resource_type: 'Bot',
actions: ['chat'],
},
],
},
},
msg: '',
}),
ListPersonalAccessTokenSupportPermissions: vi
.fn()
.mockRejectedValueOnce('error')
.mockResolvedValue({
code: 0,
data: {
permission_list: [
{
resource_type: 'Bot',
actions: ['chat'],
},
],
},
msg: '',
}),
},
PlaygroundApi: {
GetSpaceListV2: vi
.fn()
.mockRejectedValueOnce('error')
.mockResolvedValue({
code: 0,
data: {
bot_space_list: [
{
description: 'Personal Space',
hide_operation: false,
icon_url:
'https://placehold.co/460x460?text=placeholder-1',
id: '7304535597841317932',
name: 'Personal',
role_type: 1,
space_type: 1,
},
{
description: 'Public',
hide_operation: true,
icon_url:
'https://placehold.co/460x460?text=placeholder-1',
id: '7293504662404071468',
name: 'Public',
role_type: 3,
space_type: 2,
},
],
},
msg: '',
}),
},
developerBackendApi: {
GetPermissionList: vi
.fn()
.mockRejectedValueOnce('error')
.mockResolvedValue({
code: 0,
data: {
data: [
{
childrens: [
{
childrens: [],
create_time: '2024-05-06 12:03:11',
description: 'Bot相关聊天',
display_name: '',
key: 'chat',
parent_id: '7365730459227013164',
permission_id: '7365731956782284844',
update_time: '2024-05-06 12:03:11',
},
],
create_time: '2024-05-06 11:57:38',
description: 'Bot相关',
display_name: '',
key: 'Bot',
parent_id: '0',
permission_id: '7365730459227013164',
update_time: '2024-05-06 14:08:16',
},
],
},
msg: '',
}),
},
}));
vi.mock('@coze-arch/bot-flags', () => ({
getFlags: vi.fn(),
}));
vi.mock('../../src/utils/time.ts', () => ({
getExpireAt: vi.fn(() => '-'),
}));
vi.mock('@coze-arch/coze-design', () => ({}));
vi.mock('@coze-studio/bot-detail-store', () => ({
initBotDetailStore: vi.fn(),
useBotDetailStoreSet: {
clear: vi.fn(),
},
}));
vi.mock('@flow-foundation/enterprise', () => ({
useEnterpriseStore: () => ({
isCurrentEnterpriseInit: true,
}),
useCurrentEnterpriseInfo: vi.fn(),
useEnterpriseList: vi.fn(),
}));
describe('useGetPATList', () => {
it('get pat list error', () => {
const { result } = renderHook(() => useGetPATList({}));
expect(result.current.loading).toEqual(false);
act(() => result.current.fetchData());
expect(result.current.loading).toEqual(true);
});
it('get pat list success', () => {
const { result } = renderHook(() => useGetPATList({}));
expect(result.current.loading).toEqual(false);
act(() => result.current.fetchData());
expect(result.current.dataSource).toEqual([]);
expect(result.current.loading).toEqual(true);
});
});
describe('useCreatePAT', () => {
const createValue = {
name: 'Secret token',
duration_day: '1',
workspace_permission: {
option: 2,
permission_list: [{ resource_type: 'Bot', actions: ['chat'] }],
},
};
it('create error', async () => {
const { result, waitForNextUpdate } = renderHook(() => useCreatePAT());
expect(result.current.loading).toEqual(false);
act(() => result.current.runCreate(createValue));
await waitForNextUpdate();
expect(result.current.loading).toEqual(false);
});
it('create success', async () => {
const { result, waitForValueToChange } = renderHook(() => useCreatePAT());
expect(result.current.loading).toEqual(false);
act(() => result.current.runCreate(createValue));
await waitForValueToChange(() => result.current.successData);
expect(result.current.successData?.token).toEqual('pat');
expect(result.current.loading).toEqual(false);
});
});
describe('useUpdatePAT', () => {
const updateValue = {
id: 'id',
name: 'Secret token',
workspace_permission: {
option: 2,
permission_list: [{ resource_type: 'Bot', actions: ['chat'] }],
},
};
const successHandle = vi.fn();
it('update error', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useUpdatePAT({ successHandle }),
);
expect(result.current.loading).toEqual(false);
act(() => result.current.runUpdate(updateValue));
await waitForNextUpdate();
expect(result.current.loading).toEqual(false);
});
it('update success', async () => {
const { result, waitForValueToChange } = renderHook(() =>
useUpdatePAT({ successHandle }),
);
expect(result.current.loading).toEqual(false);
act(() => result.current.runUpdate(updateValue));
await waitForValueToChange(() => result.current.loading);
expect(result.current.loading).toEqual(false);
});
});
describe('useDeletePAT', () => {
const successHandle = vi.fn();
it('delete error', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useDeletePAT({ successHandle }),
);
expect(result.current.loading).toEqual(false);
act(() => result.current.runDelete('id'));
await waitForNextUpdate();
expect(result.current.loading).toEqual(false);
});
it('delete success', async () => {
const { result, waitForValueToChange } = renderHook(() =>
useDeletePAT({ successHandle }),
);
expect(result.current.loading).toEqual(false);
act(() => result.current.runDelete('id'));
await waitForValueToChange(() => result.current.loading);
expect(result.current.loading).toEqual(false);
});
});
describe('usePATPermission', () => {
let id = '1';
it('get user pat permission error', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
usePATPermission({ patId: id }),
);
await waitForNextUpdate();
expect(result.current.patPermission).toEqual(undefined);
});
it('get user pat permission success', async () => {
const { result, rerender, waitForValueToChange } = renderHook(() =>
usePATPermission({ patId: id }),
);
rerender({ patId: '2' });
await waitForValueToChange(() => result.current.patPermission);
expect(result.current.patPermission?.personal_access_token?.name).toEqual(
'Secret token',
);
id = '';
rerender({ patId: '' });
expect(result.current.patPermission).toEqual(undefined);
});
});

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,7 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {},
});

View File

@@ -0,0 +1,72 @@
{
"name": "@coze-studio/open-auth",
"version": "0.0.1",
"description": "bot open platform",
"license": "Apache-2.0",
"author": "gaoding.devingao@bytedance.com",
"maintainers": [],
"exports": {
".": "./src/index.tsx"
},
"main": "src/index.tsx",
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-arch/bot-api": "workspace:*",
"@coze-arch/bot-semi": "workspace:*",
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
"@coze-arch/i18n": "workspace:*",
"@coze-arch/logger": "workspace:*",
"@coze-arch/report-events": "workspace:*",
"ahooks": "^3.7.8",
"classnames": "^2.3.2",
"copy-to-clipboard": "^3.3.3",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21"
},
"devDependencies": {
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@rsbuild/core": "1.1.13",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/node": "18.18.9",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@types/react-helmet": "^6.1.11",
"@vitest/coverage-v8": "~3.0.5",
"axios": "^1.7.1",
"core-js": "^3.37.1",
"eventemitter3": "^5.0.1",
"less": "^3.13.1",
"less-loader": "^7.1.0",
"less-plugin-autoprefix": "^2.0.0",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"react-helmet": "^6.1.0",
"react-is": ">= 16.8.0",
"react-router-dom": "^6.22.0",
"scheduler": ">=0.19.0",
"styled-components": ">= 2",
"stylelint": "^15.11.0",
"typescript": "5.7.2",
"utility-types": "^3.10.0",
"vitest": "~3.0.5",
"webpack": "~5.91.0"
},
"peerDependencies": {
"react": ">=18.2.0",
"react-dom": ">=18.2.0"
},
"// deps": "immer@^10.0.3 为脚本自动补齐,请勿改动"
}

View File

@@ -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 { vi } from 'vitest';
vi.mock('@coze-arch/i18n', () => ({
I18n: {
t: vi.fn(),
},
}));
vi.mock('@coze-arch/logger', () => ({
logger: {
info: vi.fn(),
createLoggerWith: vi.fn(() => ({
info: vi.fn(),
persist: {
error: vi.fn(),
},
})),
},
reporter: {
errorEvent: vi.fn(),
info: vi.fn(),
},
}));
vi.stubGlobal('IS_OVERSEA', false);
vi.stubGlobal('IS_DEV_MODE', true);
vi.stubGlobal('IS_BOE', true);

View File

@@ -0,0 +1,137 @@
/* stylelint-disable declaration-no-important */
/* stylelint-disable no-descending-specificity */
/* stylelint-disable max-nesting-depth */
// 历史问题
.table-wrap {
flex: 1;
min-height: 0;
margin-right: -11px;
padding-right: 11px;
}
.table-content {
width: 100%;
:global {
.semi-table-tbody > .semi-table-row,
.semi-table-thead > .semi-table-row > .semi-table-row-head,
.semi-table-tbody > .semi-table-row:hover > .semi-table-row-cell {
background-color: transparent !important;
background-image: none !important;
}
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
height: 60px !important;
padding: 0 16px !important;
font-weight: 400 !important;
color: var(--coz-fg-primary) !important;
}
.semi-table-header::-webkit-scrollbar {
width: 4px;
background-color: transparent;
border-bottom-width: 0;
}
.semi-table-body {
padding-top: 0 !important;
.scroll-wrap;
}
.semi-table-tbody
> .semi-table-row
> .semi-table-row-cell:last-child::after,
.semi-table-tbody
> .semi-table-row
> .semi-table-row-cell:first-child::after {
display: none;
}
.semi-table-fixed-header table {
height: 48px !important;
}
.semi-table-thead > .semi-table-row > .semi-table-row-head {
padding: 0 16px !important;
color: var(--coz-fg-plus, rgba(6, 7, 9, 96%)) !important;
}
.semi-table-tbody > .semi-table-row {
cursor: default !important;
font-size: 14px !important;
}
.semi-switch:not(.semi-switch-checked) {
background-color: var(--semi-color-fill-0);
}
.semi-switch:not(.semi-switch-checked):hover {
background-color: var(--semi-color-fill-1);
}
}
&.small {
:global {
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
height: 56px !important;
padding: 0 8px !important;
}
.semi-table-fixed-header table {
height: 28px !important;
}
.semi-table-thead > .semi-table-row > .semi-table-row-head {
padding: 0 8px !important;
}
}
}
&.primary {
:global {
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
color: var(--coz-fg-primary, rgba(6, 7, 9, 80%)) !important;
}
.semi-table-fixed-header table {
height: 28px !important;
}
.semi-table-thead > .semi-table-row > .semi-table-row-head {
padding: 0 8px !important;
color: var(--coz-fg-secondary, rgba(32, 41, 69, 62%)) !important;
}
}
}
}
.scroll-wrap {
&::-webkit-scrollbar-thumb {
background: var(--coz-fg-dim, rgba(6, 7, 9, 30%));
border-radius: 3px;
}
&::-webkit-scrollbar {
display: block !important;
width: 4px;
background-color: transparent;
}
&::-webkit-scrollbar:hover {
width: 4px;
}
&::-webkit-scrollbar-button {
display: none;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-corner {
background-color: transparent;
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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 FC } from 'react';
import cls from 'classnames';
import { type TableProps, Table } from '@coze-arch/coze-design';
import styles from './index.module.less';
export const AuthTable: FC<
TableProps & {
size?: 'small' | 'default';
type?: 'primary' | 'default';
}
> = ({
wrapperClassName,
tableProps,
size = 'default',
type = 'default',
...rest
}) => (
<Table
{...rest}
wrapperClassName={cls(styles['table-wrap'], wrapperClassName)}
tableProps={{
...tableProps,
className: cls(
styles['table-content'],
tableProps?.className,
styles[size],
styles[type],
),
}}
/>
);

View File

@@ -0,0 +1,15 @@
.message-frame {
margin-bottom: 24px;
padding: 12px 16px;
font-size: 12px;
font-weight: 400;
font-style: normal;
line-height: 16px;
color: var(--coz-fg-primary);
background: var(--coz-mg-hglt, rgba(186, 192, 255, 20%));
border-radius: 8px;
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type ReactNode, type FC } from 'react';
import classNames from 'classnames';
import { I18n } from '@coze-arch/i18n';
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
import { Tooltip, Typography, Space } from '@coze-arch/coze-design';
import styles from './index.module.less';
export const LinkDocs: FC<{ text?: string; onClick?: () => void }> = ({
text,
onClick,
}) => (
<Typography.Text
className="text-[12px] !font-normal"
link
onClick={() => {
onClick?.();
}}
>
{text ? text : I18n.t('coze_api_instru')}
</Typography.Text>
);
export const PATInstructionWrap: FC<{
onClick?: () => void;
}> = ({ onClick }) => (
<div className={styles['message-frame']}>
<Space spacing={0}>
<p>{I18n.t('pat_reminder_1')}</p>
<LinkDocs onClick={onClick} />
</Space>
<p>{I18n.t('pat_reminder_2')}</p>
{IS_OVERSEA ? <p>{I18n.t('api_token_reminder_1')}</p> : null}
</div>
);
export const Tips: FC<{ tips: string | ReactNode; className?: string }> = ({
tips,
className,
}) => (
<Tooltip theme="dark" trigger="hover" content={tips}>
<div
className={classNames(
'flex items-center justify-center hover:coz-mg-secondary-hovered w-[16px] h-[16px] rounded-[4px] mr-[4px] ml-[2px] text-[12px]',
className,
)}
>
<IconCozInfoCircle className="coz-fg-secondary" />
</div>
</Tooltip>
);

View File

@@ -0,0 +1,32 @@
/* stylelint-disable declaration-no-important */
.table-container {
overflow: auto;
overflow-x: hidden;
&::-webkit-scrollbar-thumb {
background: var(--coz-fg-dim, rgba(6, 7, 9, 30%));
border-radius: 3px;
}
&::-webkit-scrollbar {
display: block !important;
width: 4px;
background-color: transparent;
}
&::-webkit-scrollbar:hover {
width: 4px;
}
&::-webkit-scrollbar-button {
display: none;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-corner {
background-color: transparent;
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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';
import cls from 'classnames';
import { I18n } from '@coze-arch/i18n';
import { type ColumnProps } from '@coze-arch/coze-design';
import { UIEmpty } from '@coze-arch/bot-semi';
import { type PersonalAccessToken } from '@coze-arch/bot-api/pat_permission_api';
import { useTableHeight } from '@/hooks/use-table-height';
import { AuthTable } from '@/components/auth-table';
import { getTableColumnConf } from './table-column';
import styles from './index.module.less';
export type GetCustomDataConfig = (options: {
onEdit: (v: PersonalAccessToken) => void;
onDelete: (id: string) => void;
}) => ColumnProps<PersonalAccessToken>[];
interface DataTableProps {
loading: boolean;
size?: 'small' | 'default';
type?: 'primary' | 'default';
dataSource: PersonalAccessToken[];
onEdit: (v: PersonalAccessToken) => void;
onDelete: (id: string) => void;
onAddClick: () => void;
renderDataEmptySlot?: () => React.ReactElement | null;
getCustomDataConfig?: GetCustomDataConfig;
}
export const DataTable = ({
loading,
dataSource,
onEdit,
onDelete,
onAddClick,
renderDataEmptySlot,
getCustomDataConfig = getTableColumnConf,
size,
type,
}: DataTableProps) => {
const tableRef = useRef<HTMLDivElement>(null);
const tableHeight = useTableHeight(tableRef);
const columns: ColumnProps<PersonalAccessToken>[] = getCustomDataConfig?.({
onEdit,
onDelete,
}).filter(item => !item.hidden);
return (
<div className={cls('flex-1', styles['table-container'])} ref={tableRef}>
<AuthTable
useHoverStyle={false}
size={size}
type={type}
tableProps={{
rowKey: 'id',
loading,
dataSource,
columns,
scroll: { y: tableHeight },
}}
empty={
renderDataEmptySlot?.() || (
<UIEmpty
empty={{
title: I18n.t('no_api_token_1'),
description: I18n.t('add_api_token_1'),
btnText: I18n.t('add_new_token_button_1'),
btnOnClick: onAddClick,
}}
/>
)
}
/>
</div>
);
};

View File

@@ -0,0 +1,28 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { type ColumnProps } from '@coze-arch/coze-design';
import { type PersonalAccessToken } from '@coze-arch/bot-api/pat_permission_api';
import { getDetailTime } from '@/utils/time';
export const columnCreateAtConf: () => ColumnProps<PersonalAccessToken> =
() => ({
title: I18n.t('coze_api_list3'),
dataIndex: 'created_at',
render: (createTime: number) => getDetailTime(createTime),
});

View File

@@ -0,0 +1,28 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { type ColumnProps } from '@coze-arch/coze-design';
import { type PersonalAccessToken } from '@coze-arch/bot-api/pat_permission_api';
import { getExpirationTime } from '@/utils/time';
export const columnExpireAtConf: () => ColumnProps<PersonalAccessToken> =
() => ({
title: I18n.t('expire_time_1'), // 状态
dataIndex: 'expire_at',
render: (expireTime: number) => getExpirationTime(expireTime),
});

View File

@@ -0,0 +1,28 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { type ColumnProps } from '@coze-arch/coze-design';
import { type PersonalAccessToken } from '@coze-arch/bot-api/pat_permission_api';
import { getDetailTime } from '@/utils/time';
export const columnLastUseAtConf: () => ColumnProps<PersonalAccessToken> =
() => ({
title: I18n.t('coze_api_list4'),
dataIndex: 'last_used_at',
render: (lastUseTime: number) => getDetailTime(lastUseTime),
});

View File

@@ -0,0 +1,25 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { type ColumnProps } from '@coze-arch/coze-design';
import { type PersonalAccessToken } from '@coze-arch/bot-api/pat_permission_api';
export const columnNameConf: () => ColumnProps<PersonalAccessToken> = () => ({
title: I18n.t('coze_api_list1'),
dataIndex: 'name',
width: 120,
render: (name: string) => <p>{name}</p>,
});

View File

@@ -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 { type FC } from 'react';
import classNames from 'classnames';
import { I18n } from '@coze-arch/i18n';
import { IconCozMinusCircle, IconCozEdit } from '@coze-arch/coze-design/icons';
import { type ColumnProps, Tooltip, Space } from '@coze-arch/coze-design';
import { UIButton, Popconfirm } from '@coze-arch/bot-semi';
import { type PersonalAccessToken } from '@coze-arch/bot-api/pat_permission_api';
import { getStatus } from '@/utils/time';
import styles from './index.module.less';
export const ColumnOpBody: FC<{
record: PersonalAccessToken;
isCurrentUser?: boolean;
onEdit: (v: PersonalAccessToken) => void;
onDelete: (id: string) => void;
afterConfirmDelete?: () => void;
afterCancelDelete?: () => void;
}> = ({
record,
isCurrentUser,
onEdit,
onDelete,
afterConfirmDelete,
afterCancelDelete,
}) => {
const isActive = getStatus(record?.expire_at as number);
return (
<Space align="center" spacing={17}>
<Tooltip
content={
isCurrentUser
? I18n.t(isActive ? 'Edit' : 'not_support_edit_1')
: I18n.t('org_api_pat_edit_reminder')
}
>
<UIButton
onClick={() => onEdit(record)}
className={classNames(styles['btn-frame'], {
[styles['btn-frame-disabled']]: !isActive,
})}
theme="borderless"
icon={<IconCozEdit className={styles.icon} />}
disabled={!isActive || !isCurrentUser}
></UIButton>
</Tooltip>
<Popconfirm
style={{ width: 400 }}
okType="danger"
trigger="click"
onConfirm={() => {
onDelete(`${record?.id}`);
afterConfirmDelete?.();
}}
onCancel={() => {
afterCancelDelete?.();
}}
content={I18n.t('remove_token_1')}
title={I18n.t('remove_token_reminder_1')}
>
<div>
<Tooltip content={I18n.t('Remove')}>
<UIButton
className={styles['btn-frame']}
theme="borderless"
icon={<IconCozMinusCircle className={styles.icon} />}
></UIButton>
</Tooltip>
</div>
</Popconfirm>
</Space>
);
};
export const columnOpConf: () => ColumnProps<PersonalAccessToken> = () => ({
title: I18n.t('coze_api_list5'),
width: 120,
render: (_: string, _record: unknown) => null,
});

View File

@@ -0,0 +1,36 @@
/*
* 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 { I18n } from '@coze-arch/i18n';
import { Tag } from '@coze-arch/coze-design';
import { type ColumnProps } from '@coze-arch/coze-design';
import { type PersonalAccessToken } from '@coze-arch/bot-api/pat_permission_api';
import { getStatus } from '@/utils/time';
export const columnStatusConf: () => ColumnProps<PersonalAccessToken> = () => ({
title: I18n.t('api_status_1'),
dataIndex: 'id',
width: 80,
render: (_: string, record: PersonalAccessToken) => {
const isActive = getStatus(record?.expire_at as number);
return (
<Tag size="small" color={isActive ? 'primary' : 'grey'}>
{I18n.t(isActive ? 'api_status_active_1' : 'api_status_expired_1')}
</Tag>
);
},
});

View File

@@ -0,0 +1,14 @@
.btn-frame {
width: 24px;
height: 24px;
.icon {
color: #6b6b75;
}
&.btn-frame-disabled {
.icon {
color: rgba(29, 28, 35, 20%);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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 ColumnProps } from '@coze-arch/coze-design';
import { type PersonalAccessToken } from '@coze-arch/bot-api/pat_permission_api';
import { columnStatusConf } from './column-status';
import { ColumnOpBody, columnOpConf } from './column-op';
import { columnNameConf } from './column-name';
import { columnLastUseAtConf } from './column-last-use-at';
import { columnExpireAtConf } from './column-expire-at';
import { columnCreateAtConf } from './column-create-at';
export const getTableColumnConf = ({
onEdit,
onDelete,
}: {
onEdit: (v: PersonalAccessToken) => void;
onDelete: (id: string) => void;
}): ColumnProps<PersonalAccessToken>[] => [
columnNameConf(),
columnCreateAtConf(),
columnLastUseAtConf(),
columnExpireAtConf(),
columnStatusConf(),
{
...columnOpConf(),
render: (_, record) => (
<ColumnOpBody {...{ record, isCurrentUser: true, onEdit, onDelete }} />
),
},
];
export const patColumn = {
columnNameConf,
columnCreateAtConf,
columnLastUseAtConf,
columnExpireAtConf,
columnStatusConf,
ColumnOpBody,
columnOpConf,
};

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect } from 'react';
import { type FetchCustomPatList } from '@/hooks/pat/use-token';
import { usePatOperation } from '@/hooks/pat/action/use-pat-operation';
import { TopBody } from './top-body';
import { ResultModal } from './result-modal';
import { PermissionModal, type PermissionModalProps } from './permission-modal';
import { DataTable, type GetCustomDataConfig } from './data-table';
export interface PATProps {
size?: 'small' | 'default';
type?: 'primary' | 'default';
renderTopBodySlot?: (options: {
openAddModal: () => void;
}) => React.ReactNode;
renderDataEmptySlot?: () => React.ReactElement | null;
getCustomDataConfig?: GetCustomDataConfig;
fetchCustomPatList?: FetchCustomPatList;
renderPermissionModal?: (options: PermissionModalProps) => void;
afterCancelPermissionModal?: (isCreate: boolean) => void;
}
export const PatBody: React.FC<PATProps> = ({
size,
type,
renderTopBodySlot,
renderDataEmptySlot,
getCustomDataConfig,
fetchCustomPatList,
renderPermissionModal,
afterCancelPermissionModal,
}) => {
const {
onAddClick,
loading,
dataSource,
editHandle,
runDelete,
refreshHandle,
showDataForm,
isCreate,
createSuccessHandle,
onCancel,
successData,
showResult,
setShowResult,
editInfo,
fetchData,
} = usePatOperation({ fetchCustomPatList, afterCancelPermissionModal });
useEffect(() => {
fetchData();
}, []);
const permissionModalOptions = {
isCreate,
onRefresh: refreshHandle,
editInfo,
onCreateSuccess: createSuccessHandle,
onCancel,
};
return (
<div className="w-full h-full flex flex-col">
{renderTopBodySlot?.({ openAddModal: onAddClick }) || (
<TopBody openAddModal={onAddClick} />
)}
<DataTable
size={size}
type={type}
loading={loading}
dataSource={dataSource}
onEdit={editHandle}
onDelete={runDelete}
onAddClick={onAddClick}
renderDataEmptySlot={renderDataEmptySlot}
getCustomDataConfig={getCustomDataConfig}
/>
{showDataForm
? renderPermissionModal?.(permissionModalOptions) || (
<PermissionModal {...permissionModalOptions} />
)
: null}
<ResultModal
data={successData}
visible={showResult}
onOk={() => setShowResult(false)}
/>
</div>
);
};

View File

@@ -0,0 +1,11 @@
.expiration-select {
display: flex;
gap: 8px;
:global {
.semi-form-field {
width: 100%;
padding: 0;
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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 FC, useState } from 'react';
import { I18n } from '@coze-arch/i18n';
import { Form, Input } from '@coze-arch/coze-design';
import { type GetPersonalAccessTokenAndPermissionResponseData } from '@coze-arch/bot-api/pat_permission_api';
import {
ExpirationDate,
disabledDate,
getExpirationOptions,
getExpirationTime,
} from '@/utils/time';
import { Tips } from '@/components/instructions-wrap';
import styles from './index.module.less';
export const CommonFormParams: FC<{
isCreate?: boolean;
patPermission?: GetPersonalAccessTokenAndPermissionResponseData;
}> = ({ isCreate, patPermission }) => {
const [durationDay, setDurationDay] = useState<ExpirationDate>();
const dataOptionsList = getExpirationOptions();
return (
<>
<Form.Input
trigger={['blur', 'change']}
field="name"
label={{
text: I18n.t('coze_api_list1'),
required: true,
}}
placeholder={''}
maxLength={20}
rules={[{ required: true, message: '' }]}
/>
<Form.Slot
label={{
text: I18n.t('expire_time_1'),
required: true,
extra: <Tips tips={I18n.t('expired_time_forbidden_1')} />,
}}
>
{isCreate ? (
<>
<div className={styles['expiration-select']}>
<Form.Select
noLabel={true}
field="duration_day"
style={{ width: '100%' }}
disabled={!isCreate}
optionList={dataOptionsList}
onChange={v => setDurationDay(v as ExpirationDate)}
rules={[{ required: true, message: '' }]}
placeholder={I18n.t('select_expired_time_1')}
/>
{durationDay === ExpirationDate.CUSTOMIZE && (
<Form.DatePicker
noLabel={true}
field="expire_at"
style={{ width: '100%' }}
disabled={!isCreate}
disabledDate={disabledDate}
position="bottomRight"
/>
)}
</div>
</>
) : (
<Input
disabled
value={
patPermission?.personal_access_token?.expire_at
? getExpirationTime(
patPermission?.personal_access_token?.expire_at as number,
)
: ''
}
/>
)}
</Form.Slot>
</>
);
};

View File

@@ -0,0 +1,72 @@
.permission-form-content {
overflow-y: auto;
width: 417px;
max-height: 522px;
margin-top: 16px;
margin-right: -11px;
padding-right: 8px;
.scroll-wrap;
:global {
/* stylelint-disable-next-line no-descending-specificity */
.semi-form-field {
padding: 0;
padding-bottom: 16px;
}
.semi-form-field-label {
margin-bottom: 6px;
padding: 0 8px;
font-size: 12px;
font-weight: 500;
font-style: normal;
line-height: 16px;
color: var(--coz-fg-primary);
}
.semi-form-field-error-message {
padding-left: 8px;
}
.semi-form-field-main {
padding: 0;
}
.semi-form-field-label-required .semi-form-field-label-text::after,
.semi-form-field-label-with-extra .semi-form-field-label-extra {
margin-left: 0;
}
}
}
.scroll-wrap {
&::-webkit-scrollbar-thumb {
background: var(--coz-fg-dim, rgba(6, 7, 9, 30%));
border-radius: 3px;
}
&::-webkit-scrollbar {
// stylelint-disable-next-line declaration-no-important
display: block !important;
width: 4px;
background-color: transparent;
}
&::-webkit-scrollbar:hover {
width: 4px;
}
&::-webkit-scrollbar-button {
display: none;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-corner {
background-color: transparent;
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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 {
forwardRef,
type PropsWithChildren,
useEffect,
useImperativeHandle,
useRef,
} from 'react';
import { I18n } from '@coze-arch/i18n';
import {
type PersonalAccessToken,
type CreatePersonalAccessTokenAndPermissionResponseData,
type GetPersonalAccessTokenAndPermissionResponseData,
} from '@coze-arch/bot-api/pat_permission_api';
import { Form, type FormApi, Modal, Spin, Toast } from '@coze-arch/coze-design';
import { usePatForm, type FormApiInfo } from '@/hooks/pat/action/use-pat-form';
import { CommonFormParams } from './common-form-params';
import styles from './index.module.less';
export interface PermissionModalProps {
editInfo?: PersonalAccessToken;
isCreate: boolean;
isReady?: boolean;
onRefresh: () => void;
onCreateSuccess: (
v: CreatePersonalAccessTokenAndPermissionResponseData,
) => void;
onCancel: () => void;
onPatPermissionChange?: (
data?: GetPersonalAccessTokenAndPermissionResponseData,
) => void;
onCustomFormValueChange?: (values: unknown, changedValue: unknown) => void;
validateCustomParams?: () => boolean;
getCustomParams?: () => Record<string, unknown>;
afterSubmit?: (params: Record<string, unknown>) => void;
isShowAuthMigrateNotice?: boolean;
}
export interface PermissionModalRef {
setFormValue: (key: string, value: unknown) => void;
validateParams: () => void;
getFormValues: () => Record<string, unknown>;
}
export const PermissionModal = forwardRef(function PermissionModal(
{
editInfo,
isCreate,
onRefresh,
onCreateSuccess,
onCancel,
children,
onPatPermissionChange,
onCustomFormValueChange,
validateCustomParams,
getCustomParams,
afterSubmit,
isReady = true,
isShowAuthMigrateNotice = false,
}: PropsWithChildren<PermissionModalProps>,
ref,
) {
const formApi = useRef<FormApi<FormApiInfo>>();
const {
isFailToValid,
ready,
loading,
onSubmit,
onFormValueChange,
patPermission,
successData,
updateSuccessData,
validateParams,
} = usePatForm({
editInfo,
isCreate,
formApi,
validateCustomParams,
getCustomParams,
afterSubmit,
isShowAuthMigrateNotice,
});
const modalReady = isReady && ready;
useEffect(() => {
if (successData) {
Toast.success({ content: I18n.t('Create_success'), showClose: false });
onCreateSuccess(successData);
onRefresh();
}
}, [successData]);
useEffect(() => {
if (updateSuccessData) {
Toast.success({ content: I18n.t('Edit_success'), showClose: false });
onRefresh();
}
}, [updateSuccessData]);
useImperativeHandle(
ref,
() => ({
setFormValue: (key: string, value: unknown) => {
formApi.current?.setValue(key as keyof FormApiInfo, value);
},
getFormValues: () => formApi.current?.getValues(),
validateParams,
}),
[validateParams],
);
useEffect(() => {
onPatPermissionChange?.(patPermission);
}, [patPermission]);
return (
<Modal
title={isCreate ? I18n.t('add_new_pat_1') : I18n.t('edit_pat_1')}
visible={true}
width={480}
centered
maskClosable={false}
onCancel={onCancel}
onOk={onSubmit}
okButtonProps={{
disabled: isFailToValid || !modalReady,
loading,
}}
cancelText={I18n.t('cancel')}
okText={I18n.t('confirm')}
>
<Spin spinning={!modalReady}>
<div className={styles['permission-form-content']}>
<Form<FormApiInfo>
showValidateIcon={false}
getFormApi={api => (formApi.current = api)}
onValueChange={(values, changedValue) => {
if (onCustomFormValueChange) {
onCustomFormValueChange(values, changedValue);
} else {
onFormValueChange(values, changedValue as FormApiInfo);
}
}}
>
<CommonFormParams
isCreate={isCreate}
patPermission={patPermission}
/>
{children}
</Form>
</div>
</Spin>
</Modal>
);
});

View File

@@ -0,0 +1,66 @@
.result-frame {
:global {
.semi-modal-body {
// stylelint-disable-next-line declaration-no-important
padding: 0 !important;
}
}
.warn-text {
margin-bottom: 32px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1d1c23);
}
.title-text {
margin-bottom: 8px;
font-size: 14px;
font-weight: 600;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1d1c23);
}
.para {
overflow: hidden;
max-width: 90%;
margin-bottom: 36px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1d1c23);
text-overflow: ellipsis;
}
.key-text {
overflow: hidden;
max-width: 487px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
color: var(--Light-usage-text---color-text-0, #1d1c23);
text-overflow: ellipsis;
}
.sp {
width: 100%;
margin-bottom: 44px;
.icon {
cursor: pointer;
width: 16px;
height: 16px;
svg {
color: rgba(77, 83, 232, 100%);
}
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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 copy from 'copy-to-clipboard';
import { useMemoizedFn } from 'ahooks';
import { I18n } from '@coze-arch/i18n';
import { IconCozCopy } from '@coze-arch/coze-design/icons';
import {
UIModal,
Typography,
Toast,
Space,
Tooltip,
} from '@coze-arch/bot-semi';
import { type CreatePersonalAccessTokenAndPermissionResponseData } from '@coze-arch/bot-api/pat_permission_api';
import { getExpirationTime } from '@/utils/time';
import s from './index.module.less';
interface ResultProps {
data?: CreatePersonalAccessTokenAndPermissionResponseData;
visible: boolean;
onOk: () => void;
}
// 新建编辑 PAT
export const ResultModal = ({ visible, onOk, data }: ResultProps) => {
const doCopyAsync = useMemoizedFn(() => {
const targetKey = data?.token;
if (targetKey) {
doCopy(targetKey);
}
});
const doCopy = useMemoizedFn(targetText => {
const res = copy(targetText);
if (!res) {
throw new Error('custom error');
}
Toast.success({
content: I18n.t('token_copied_1'),
showClose: false,
});
});
return (
<UIModal
className={s['result-frame']}
title={I18n.t('new_pat_1')}
visible={visible}
width={560}
centered
onOk={onOk}
onCancel={onOk}
okText={I18n.t('confirm')}
footer={null}
>
<p className={s['warn-text']}>{I18n.t('new_pat_reminder_1')}</p>
<p className={s['title-text']}>{I18n.t('coze_api_list1')}</p>
<Typography.Paragraph className={s.para} ellipsis={{ rows: 1 }}>
{data?.personal_access_token?.name ?? '-'}
</Typography.Paragraph>
<p className={s['title-text']}>{I18n.t('expire_time_1')}</p>
<Typography.Paragraph className={s.para} ellipsis={{ rows: 1 }}>
{getExpirationTime(data?.personal_access_token.expire_at as number)}
</Typography.Paragraph>
<p className={s['title-text']}>{I18n.t('token_key_1')}</p>
<Space spacing={4} className={s.sp}>
<Typography.Paragraph className={s['key-text']} ellipsis={{ rows: 1 }}>
{data?.token}
</Typography.Paragraph>
<Tooltip content={I18n.t('Copy')}>
<IconCozCopy className={s.icon} onClick={doCopyAsync} />
</Tooltip>
</Space>
</UIModal>
);
};

View File

@@ -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 { type FC } from 'react';
import { I18n } from '@coze-arch/i18n';
import { Button, Space } from '@coze-arch/coze-design';
import { PATInstructionWrap } from '@/components/instructions-wrap';
export const TopBody: FC<{
openAddModal: () => void;
}> = ({ openAddModal }) => (
<Space vertical spacing={20}>
<Space className="w-full">
<h3 className="flex-1 m-0">{I18n.t('auth_tab_pat')}</h3>
<Button onClick={openAddModal} theme="solid" type="primary">
{I18n.t('add_new_token_button_1')}
</Button>
</Space>
<div className="w-full">
<PATInstructionWrap
onClick={() => {
window.open(
IS_OVERSEA
? // cp-disable-next-line
'https://www.coze.com/open/docs/developer_guides/coze_api_overview'
: // cp-disable-next-line
'https://www.coze.cn/open/docs/developer_guides/coze_api_overview',
);
}}
/>
</div>
</Space>
);

View File

@@ -0,0 +1,177 @@
/*
* 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, useState } from 'react';
import { I18n } from '@coze-arch/i18n';
import { type PersonalAccessToken } from '@coze-arch/bot-api/pat_permission_api';
import { Modal, type FormApi } from '@coze-arch/coze-design';
import { ExpirationDate, getExpireAt } from '@/utils/time';
import {
usePATPermission,
useCreatePAT,
useUpdatePAT,
} from '@/hooks/pat/use-token';
export interface FormApiInfo {
name: string;
duration_day: ExpirationDate;
expire_at: Date;
}
interface PatFormProps {
editInfo?: PersonalAccessToken;
isCreate: boolean;
isShowAuthMigrateNotice?: boolean;
formApi: React.MutableRefObject<FormApi<FormApiInfo> | undefined>;
validateCustomParams?: () => boolean;
getCustomParams?: () => Record<string, unknown>;
afterSubmit?: (params: Record<string, unknown>) => void;
}
const getDurationData = (durationDay: ExpirationDate, expireAt: Date) => ({
duration_day: durationDay,
...(durationDay === ExpirationDate.CUSTOMIZE
? { expire_at: getExpireAt(expireAt as Date) }
: {}),
});
const validateName = (name?: string) => Boolean(name);
const validateDuration = (durationDay?: ExpirationDate, expireAt?: Date) => {
if (!durationDay) {
return false;
}
if (durationDay === ExpirationDate.CUSTOMIZE && !expireAt) {
return false;
}
return true;
};
const authMigrateNoticeLSKey = 'auth_migrate_notice_do_not_show_again';
const useAuthMigrateNotice = (isShowAuthMigrateNotice?: boolean) => {
useEffect(() => {
if (!isShowAuthMigrateNotice) {
return;
}
if (!localStorage.getItem(authMigrateNoticeLSKey)) {
Modal.info({
title: I18n.t('api_permissionkey_notification_title'),
content: I18n.t('api_permissionkey_notification_content'),
okText: I18n.t('got_it'),
onOk: () => {
localStorage.setItem(authMigrateNoticeLSKey, 'true');
},
showCancelButton: false,
closable: false,
maskClosable: false,
});
}
}, []);
};
export const usePatForm = ({
editInfo,
isCreate,
formApi,
getCustomParams,
validateCustomParams,
afterSubmit,
isShowAuthMigrateNotice,
}: PatFormProps) => {
const { patPermission } = usePATPermission({
patId: editInfo?.id,
});
const { loading: createLoading, runCreate, successData } = useCreatePAT();
const {
loading: updateLoading,
runUpdate,
updateSuccessData,
} = useUpdatePAT();
const [isFailToValid, setIsFailToValid] = useState(true);
const onSubmit = () => {
const {
name = '',
duration_day,
expire_at,
} = formApi.current?.getValues() || {};
const params = {
name,
...(getCustomParams?.() || {}),
};
if (isCreate) {
runCreate({
...params,
...getDurationData(duration_day as ExpirationDate, expire_at as Date),
});
} else {
runUpdate({ ...params, id: editInfo?.id ?? '' });
}
afterSubmit?.({ ...params, duration_day, expire_at });
};
const validateParams = () => {
const { name, duration_day, expire_at } =
formApi.current?.getValues() || {};
const nameValid = validateName(name);
const isCustomParamsValid = validateCustomParams?.() !== false;
const durationValid = isCreate
? validateDuration(duration_day, expire_at)
: true;
setIsFailToValid(!(nameValid && isCustomParamsValid && durationValid));
};
const onFormValueChange = (
_values: FormApiInfo,
_changedValue: FormApiInfo,
) => {
validateParams();
};
useEffect(() => {
if (isCreate) {
formApi.current?.setValue('name', 'Secret token');
} else if (patPermission && patPermission?.personal_access_token?.name) {
formApi.current?.setValue(
'name',
patPermission?.personal_access_token?.name,
);
}
}, [patPermission]);
const ready = isCreate ? true : !!patPermission;
useAuthMigrateNotice(isShowAuthMigrateNotice);
return {
isFailToValid,
ready,
loading: updateLoading || createLoading,
onSubmit,
onFormValueChange,
patPermission,
validateParams,
successData,
updateSuccessData,
};
};

View File

@@ -0,0 +1,99 @@
/*
* 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 } from 'react';
import { I18n } from '@coze-arch/i18n';
import { Toast } from '@coze-arch/coze-design';
import {
type CreatePersonalAccessTokenAndPermissionResponseData,
type PersonalAccessToken,
} from '@coze-arch/bot-api/pat_permission_api';
import {
useDeletePAT,
useGetPATList,
type FetchCustomPatList,
} from '../use-token';
export const usePatOperation = ({
fetchCustomPatList,
afterCancelPermissionModal,
}: {
fetchCustomPatList?: FetchCustomPatList;
afterCancelPermissionModal?: (isCreate: boolean) => void;
}) => {
const { loading, dataSource, fetchData } = useGetPATList({
fetchCustomPatList,
});
const { runDelete } = useDeletePAT({
successHandle: () => {
Toast.success({ content: I18n.t('Delete_success'), showClose: false });
fetchData();
},
});
const [showDataForm, setShowDataForm] = useState(false);
const [showResult, setShowResult] = useState(false);
const [isCreate, setIsCreate] = useState(true);
const [editInfo, setEditInfo] = useState<PersonalAccessToken>();
const [successData, setSuccessData] =
useState<CreatePersonalAccessTokenAndPermissionResponseData>();
const onAddClick = () => {
setIsCreate(true);
setShowDataForm(true);
};
const editHandle = (v: PersonalAccessToken) => {
setEditInfo(v);
setIsCreate(false);
setShowDataForm(true);
};
const onCancel = () => {
setShowDataForm(false);
setEditInfo(undefined);
afterCancelPermissionModal?.(isCreate);
};
const createSuccessHandle = (
data: CreatePersonalAccessTokenAndPermissionResponseData,
) => {
setSuccessData(data);
setEditInfo(undefined);
setShowResult(true);
};
const refreshHandle = () => {
fetchData();
setShowDataForm(false);
setEditInfo(undefined);
};
return {
dataSource,
loading,
showDataForm,
setShowDataForm,
isCreate,
editInfo,
successData,
onAddClick,
createSuccessHandle,
refreshHandle,
editHandle,
runDelete,
onCancel,
setIsCreate,
showResult,
setShowResult,
fetchData,
};
};

View File

@@ -0,0 +1,236 @@
/*
* 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, useState } from 'react';
import { useMemoizedFn, useRequest } from 'ahooks';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { reporter } from '@coze-arch/logger';
import {
type PersonalAccessToken,
type CreatePersonalAccessTokenAndPermissionRequest,
type UpdatePersonalAccessTokenAndPermissionRequest,
type CreatePersonalAccessTokenAndPermissionResponseData,
type GetPersonalAccessTokenAndPermissionResponseData,
type ListPersonalAccessTokensResponse2,
} from '@coze-arch/bot-api/pat_permission_api';
import { patPermissionApi } from '@coze-arch/bot-api';
export type FetchCustomPatList =
() => Promise<ListPersonalAccessTokensResponse2>;
export const useGetPATList = ({
fetchCustomPatList,
}: {
fetchCustomPatList?: FetchCustomPatList;
}) => {
const [dataSource, setDataSource] = useState<PersonalAccessToken[]>([]);
const fetchPatList = useMemoizedFn(() => {
if (fetchCustomPatList) {
return fetchCustomPatList();
}
return patPermissionApi.ListPersonalAccessTokens({});
});
const { loading, run: fetchData } = useRequest(fetchPatList, {
manual: true,
onSuccess: dataSourceData => {
setDataSource(dataSourceData?.data?.personal_access_tokens);
reporter.event({
eventName: REPORT_EVENTS.openGetPatList,
meta: {
level: 'success',
action: 'ListPersonalAccessTokens',
},
});
},
onError: error => {
reporter.errorEvent({
eventName: REPORT_EVENTS.openGetPatList,
error,
meta: {
action: 'ListPersonalAccessTokens',
},
});
},
});
return {
dataSource,
loading,
fetchData,
};
};
export const useCreatePAT = () => {
const [successData, setSuccessData] =
useState<CreatePersonalAccessTokenAndPermissionResponseData>();
const { loading, run: runCreate } = useRequest(
(info: CreatePersonalAccessTokenAndPermissionRequest) =>
patPermissionApi.CreatePersonalAccessTokenAndPermission(info),
{
manual: true,
onSuccess: dataSourceData => {
setSuccessData(dataSourceData?.data);
reporter.event({
eventName: REPORT_EVENTS.openPatAction,
meta: {
level: 'success',
action: 'CreatePersonalAccessTokenAndPermission',
},
});
},
onError: error => {
reporter.errorEvent({
eventName: REPORT_EVENTS.openPatAction,
error,
meta: {
action: 'CreatePersonalAccessTokenAndPermission',
},
});
},
},
);
return {
runCreate,
loading,
successData,
};
};
export const useUpdatePAT = (
handle: {
successHandle?: () => void;
} = {},
) => {
const {
loading,
run: runUpdate,
data: updateSuccessData,
} = useRequest(
(info: UpdatePersonalAccessTokenAndPermissionRequest) =>
patPermissionApi.UpdatePersonalAccessTokenAndPermission(info),
{
manual: true,
onSuccess: () => {
handle?.successHandle?.();
reporter.event({
eventName: REPORT_EVENTS.openPatAction,
meta: {
level: 'success',
action: 'UpdatePersonalAccessTokenAndPermission',
},
});
},
onError: error => {
reporter.errorEvent({
eventName: REPORT_EVENTS.openPatAction,
error,
meta: {
action: 'UpdatePersonalAccessTokenAndPermission',
},
});
},
},
);
return {
runUpdate,
loading,
updateSuccessData,
};
};
export const useDeletePAT = ({
successHandle,
}: {
successHandle: () => void;
}) => {
const { loading, runAsync } = useRequest(
(id: string) =>
patPermissionApi.DeletePersonalAccessTokenAndPermission({ id }),
{
manual: true,
},
);
const runDelete = async (id: string) => {
try {
await runAsync(id);
successHandle();
reporter.event({
eventName: REPORT_EVENTS.openPatAction,
meta: {
level: 'success',
action: 'DeletePersonalAccessTokenAndPermission',
},
});
} catch (error) {
reporter.errorEvent({
eventName: REPORT_EVENTS.openPatAction,
error: error as Error,
meta: {
action: 'DeletePersonalAccessTokenAndPermission',
},
});
}
};
return {
runDelete,
loading,
};
};
export const usePATPermission = ({ patId }: { patId?: string }) => {
const [patPermission, setPatPermission] =
useState<GetPersonalAccessTokenAndPermissionResponseData>();
const { error: detailError, run } = useRequest(
(id: string) =>
patPermissionApi.GetPersonalAccessTokenAndPermission({ id }),
{
manual: true,
onSuccess: dataSourceData => {
setPatPermission(dataSourceData.data);
reporter.event({
eventName: REPORT_EVENTS.openGetPatList,
meta: {
level: 'success',
action: 'GetPersonalAccessTokenAndPermission',
},
});
},
onError: error => {
reporter.errorEvent({
eventName: REPORT_EVENTS.openGetPatList,
error,
meta: {
action: 'GetPersonalAccessTokenAndPermission',
},
});
},
},
);
useEffect(() => {
if (patId) {
run(patId);
} else {
setPatPermission(undefined);
}
}, [patId]);
return {
patPermission,
detailError,
};
};

View File

@@ -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, useEffect } from 'react';
export const useTableHeight = (tableRef: React.RefObject<HTMLDivElement>) => {
const [tableHeight, setTableHeight] = useState<string>('calc(100vh - 360px)');
useEffect(() => {
if (!tableRef.current) {
return;
}
const calculateHeight = () => {
if (tableRef.current) {
const topPosition = tableRef.current.getBoundingClientRect().top;
setTableHeight(`calc(100vh - ${topPosition + 80}px)`);
}
};
calculateHeight();
window.addEventListener('resize', calculateHeight);
return () => {
window.removeEventListener('resize', calculateHeight);
};
}, [tableRef.current]);
return tableHeight;
};

View File

@@ -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.
*/
export { type PATProps, PatBody } from './components/pat';
export {
disabledDate,
ExpirationDate,
getExpirationOptions,
getExpireAt,
getDetailTime,
getExpirationTime,
getStatus,
} from './utils/time';
export {
LinkDocs,
PATInstructionWrap,
Tips,
} from './components/instructions-wrap';
export { useTableHeight } from './hooks/use-table-height';
export { patColumn } from './components/pat/data-table/table-column';
export { AuthTable } from './components/auth-table';
export {
PermissionModal,
type PermissionModalProps,
type PermissionModalRef,
} from './components/pat/permission-modal';

View 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' />

View File

@@ -0,0 +1,111 @@
/*
* 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 dayjs from 'dayjs';
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
const MAX_EXPIRATION_DAYS = 30;
// 1-30天有效期
export const disabledDate = (date?: Date) => {
const today = dayjs().startOf('day'); // 当天的开始时间
const thirtyDaysLater = today.add(MAX_EXPIRATION_DAYS, 'day'); // 30天后的日期
return (
dayjs(date).isBefore(today, 'day') ||
dayjs(date).isSame(today, 'day') ||
dayjs(date).isAfter(thirtyDaysLater, 'day')
);
};
export enum ExpirationDate {
ONE = '1',
THIRTY = '30',
CUSTOMIZE = 'customize',
}
enum ServerTimeValue {
PERMANENT = -1,
NOT_USE = -1,
}
export const getExpirationOptions = () => {
const dataOptionsList = [
{
label: '1天',
value: ExpirationDate.ONE,
},
{
label: '30天',
value: ExpirationDate.THIRTY,
},
{
label: I18n.t('customize_key_1'),
value: ExpirationDate.CUSTOMIZE,
},
];
const newOptions = dataOptionsList.map(item => {
const { value } = item;
if (value === ExpirationDate.CUSTOMIZE) {
return item;
}
const currentDate = dayjs();
const futureDate = currentDate.add(Number(value), 'day');
const date = futureDate.format('YYYY-MM-DD');
return {
label: I18n.t('expired_time_days_1' as I18nKeysNoOptionsType, {
num: Number(value),
date,
}),
value,
};
});
return newOptions;
};
export const getExpireAt = (d: Date) => {
const h = 23;
const m = 59;
const s = 59;
const intDate = dayjs(d)
.add(h, 'hour')
.add(m, 'minute')
.add(s, 'second')
.unix();
return intDate;
};
export const getDetailTime = (d: number) => {
if (d === ServerTimeValue.NOT_USE) {
return '-';
}
const showDate = dayjs.unix(d).format('YYYY-MM-DD HH:mm:ss');
return showDate;
};
export const getExpirationTime = (d: number) => {
if (d === ServerTimeValue.PERMANENT) {
return I18n.t('api_status_permanent_1');
}
const showDate = dayjs.unix(d).format('YYYY-MM-DD');
return showDate;
};
export const getStatus = (d: number) => {
if (d === ServerTimeValue.PERMANENT) {
return true;
}
const current = dayjs().unix();
return d >= current;
};

View File

@@ -0,0 +1,48 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
},
"types": [],
"strictNullChecks": true,
"noImplicitAny": true,
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["src"],
"references": [
{
"path": "../../../arch/bot-api/tsconfig.build.json"
},
{
"path": "../../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../../arch/i18n/tsconfig.build.json"
},
{
"path": "../../../arch/logger/tsconfig.build.json"
},
{
"path": "../../../arch/report-events/tsconfig.build.json"
},
{
"path": "../../../components/bot-semi/tsconfig.build.json"
},
{
"path": "../../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../../config/stylelint-config/tsconfig.build.json"
},
{
"path": "../../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../../config/vitest-config/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,18 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true,
"paths": {
"@/*": ["./src/*"]
}
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
],
"exclude": ["**/*"]
}

View File

@@ -0,0 +1,27 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": [
"__tests__",
"stories",
"vitest.config.ts",
"tailwind.config.ts",
"setup"
],
"exclude": ["./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"outDir": "./dist",
"paths": {
"@/*": ["./src/*"]
},
"types": ["vitest/globals"],
"strictNullChecks": true,
"noImplicitAny": true
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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',
plugins: [
{
name: 'edenx-virtual-modules',
enforce: 'pre',
},
],
test: {
setupFiles: ['./setup'],
includeSource: ['./src'],
coverage: {
all: true,
include: ['src'],
exclude: [
'src/**/*.tsx',
'src/index.tsx',
'src/global.d.ts',
'src/typings.d.ts',
'src/components/**',
'src/pages/**',
'src/constants/**',
'src/utils/public-private-keys.ts', // window 提供的 api
'src/utils/docs.ts', // 线上未使用 仅 boe使用 即将删除
'src/utils/time.ts', // dayjs api 的调用
'src/utils//analytics/index.ts',
'src/utils//analytics/chart.ts', // 有图表 dom 相关内容
'src/hooks/pat/action/**', // 操作类 hook
'src/hooks/oauth-app/action/**', // 操作类 hook
'src/hooks/use-arcosite.ts', // 线上未使用 仅 boe使用
'src/hooks/use-show-mask.ts', // 主要为获取 dom 的 scrollTop
'src/hooks/use-docs-path.ts', // useNavigate 相关内容
],
},
},
},
{
fixSemi: true,
},
);

View File

@@ -0,0 +1,8 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
rules: {
'order/properties-order': null,
},
});

View File

@@ -0,0 +1,69 @@
# @coze-studio/open-chat
Coze Web ChatApp SDK
## Overview
This package is part of the Coze Studio monorepo and provides chat & communication functionality. It includes component.
## Getting Started
### Installation
Add this package to your `package.json`:
```json
{
"dependencies": {
"@coze-studio/open-chat": "workspace:*"
}
}
```
Then run:
```bash
rush update
```
### Usage
```typescript
import { /* exported functions/components */ } from '@coze-studio/open-chat';
// Example usage
// TODO: Add specific usage examples
```
## Features
- Component
## API Reference
### Exports
- `BuilderChat,
ChatType,
RawMessageType,`
- `Layout`
For detailed API documentation, please refer to the TypeScript definitions.
## Development
This package is built with:
- TypeScript
- React
- 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

View 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.
*/
vi.stubGlobal('IS_DEV_MODE', false);
vi.stubGlobal('IS_BOE', false);
vi.stubGlobal('IS_RELEASE_VERSION', true);
vi.stubGlobal('REGION', 'cn');

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,15 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {
'@coze-arch/max-line-per-function': [
'error',
{
max: 300,
},
],
'no-restricted-imports': 'off',
},
});

View File

@@ -0,0 +1,62 @@
{
"name": "@coze-studio/open-chat",
"version": "0.0.1",
"description": "Coze Web ChatApp SDK ",
"license": "Apache-2.0",
"author": "gaoding.devingao@bytedance.com",
"maintainers": [
"gaoding.devingao@bytedance.com"
],
"sideEffects": false,
"exports": {
".": "./src/index.ts"
},
"main": "src/index.ts",
"types": "./src/index.ts",
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "npm run test -- --coverage"
},
"dependencies": {
"@coze-arch/i18n": "workspace:*",
"@coze/chat-sdk": "0.1.11-beta.17",
"react": "~18.2.0"
},
"devDependencies": {
"@coze-arch/bot-env": "workspace:*",
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/pkg-root-webpack-plugin": "workspace:*",
"@coze-arch/postcss-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/tailwind-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@coze-studio/open-env-adapter": "workspace:*",
"@rspack/plugin-react-refresh": "0.6.0",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/node": "^18",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"concurrently": "~8.2.2",
"css-loader": "^6.10.0",
"debug": "^4.3.4",
"style-loader": "^3.3.4",
"tailwindcss": "~3.3.3",
"ts-node": "^10.9.1",
"typescript": "~5.8.2",
"vitest": "~3.0.5",
"webpack": "~5.91.0"
},
"// deps": "debug@^4.3.4 为脚本自动补齐,请勿改动",
"botPublishConfig": {
"main": "dist/index.js"
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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 '@coze/chat-sdk/webCss';
import { forwardRef, useMemo, type Ref, useImperativeHandle } from 'react';
import {
openApiHostByRegionWithToken,
openApiCdnUrlByRegion,
} from '@coze-studio/open-env-adapter';
import { I18n } from '@coze-arch/i18n';
import ChatSdk from '@coze/chat-sdk/webJs';
import {
type RawMessage,
type IChatFlowProps,
type Language,
} from '@coze/chat-sdk';
import { type IBuilderChatProps, type BuilderChatRef } from '@/types';
const {
ChatFlowFramework,
ChatSlot,
useSendMessage,
ChatType,
RawMessageType,
} = ChatSdk;
export { ChatType, RawMessageType };
export const ChatContent = forwardRef(
(_props: {}, ref: Ref<BuilderChatRef>) => {
const { sendMessage } = useSendMessage();
useImperativeHandle(
ref,
() => ({
sendMessage: (message: RawMessage) => {
sendMessage(message);
},
}),
[sendMessage],
);
return <ChatSlot />;
},
);
export const BuilderChat = forwardRef(
(props: IBuilderChatProps, ref: Ref<BuilderChatRef>) => {
const { workflow } = props;
const eventCallbacks: IChatFlowProps['eventCallbacks'] = useMemo(
() => ({
onImageClick: props.eventCallbacks?.onImageClick,
onGetChatFlowExecuteId: props.eventCallbacks?.onGetChatFlowExecuteId,
onThemeChange: props.eventCallbacks?.onThemeChange,
onInitSuccess: props.eventCallbacks?.onInitSuccess,
message: {
afterMessageReceivedFinish:
props.eventCallbacks?.afterMessageReceivedFinish,
},
}),
[props.eventCallbacks],
);
const { userInfo } = props;
const { auth } = props;
const areaUi: IChatFlowProps['areaUi'] = useMemo(
// eslint-disable-next-line complexity
() => ({
layout: props.project?.layout,
isDisabled: props.areaUi?.isDisabled,
input: {
isNeed: props.areaUi?.input?.isShow,
isNeedAudio: props.areaUi?.input?.isNeedAudio ?? !IS_OVERSEA,
placholder: props.areaUi?.input?.placeholder,
isNeedTaskMessage: props.areaUi?.input?.isNeedTaskMessage,
defaultText: props.areaUi?.input?.defaultText,
renderChatInputTopSlot: props.areaUi?.input?.renderChatInputTopSlot,
},
clearContext:
props.project?.mode === 'websdk'
? {
isNeed: true,
position: 'inputLeft',
}
: {
isNeed: false,
},
clearMessage:
props.project?.mode === 'websdk'
? {
isNeed: true,
position: 'headerRight',
}
: {
isNeed:
props.areaUi?.isNeedClearMessage !== undefined
? props.areaUi?.isNeedClearMessage
: true,
position: 'inputLeft',
},
uploadBtn: {
isNeed: props.areaUi?.uploadable,
},
uiTheme: props.areaUi?.uiTheme,
renderLoading: props.areaUi?.renderLoading,
header: {
isNeed: props.areaUi?.header?.isShow || false,
icon: props.project?.iconUrl,
title: props.project?.name,
renderRightSlot: () => <>{props.areaUi?.header?.extra || null}</>,
},
footer: props.areaUi?.footer,
}),
[props.areaUi, props.project],
);
const setting: IChatFlowProps['setting'] = useMemo(
() => ({
apiBaseUrl: openApiHostByRegionWithToken,
cdnBaseUrlPath: openApiCdnUrlByRegion,
language: I18n.language as Language,
logLevel: IS_BOE ? 'debug' : 'release',
...(props.setting || {}),
}),
[],
);
const project: IChatFlowProps['project'] = useMemo(
() => ({
id: props.project?.id || '',
type: props.project?.type,
mode: props.project?.mode as 'release',
caller: props.project?.caller,
defaultName: props.project?.defaultName,
defaultIconUrl: props.project?.defaultIconUrl,
connectorId: props.project?.connectorId,
conversationName: props.project?.conversationName,
name: props.project?.name,
iconUrl: props.project?.iconUrl,
OnBoarding: props.project?.onBoarding,
}),
[props.project],
);
return (
<>
<ChatFlowFramework
workflow={workflow}
project={project}
userInfo={userInfo}
eventCallbacks={eventCallbacks}
auth={auth}
style={props.style}
areaUi={areaUi}
setting={setting}
>
<ChatContent ref={ref} />
</ChatFlowFramework>
</>
);
},
);

View 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 {
BuilderChat,
ChatType,
RawMessageType,
} from './components/builder-chat';
export { Layout } from './types';
export type {
IWorkflow,
IProject,
IEventCallbacks,
IBuilderChatProps,
BuilderChatRef,
DebugProps,
HeaderConfig,
} from './types';

View 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.
*/
import { type ReactNode } from 'react';
export enum Layout {
PC = 'pc',
MOBILE = 'mobile',
}
export interface HeaderConfig {
isShow?: boolean; //是否显示header 默认是true
isNeedClose?: boolean; //是否需要关闭按钮, 默认是true
extra?: ReactNode | false; // 用于站位的,默认无
}
export interface DebugProps {
cozeApiRequestHeader?: Record<string, string>;
}

View File

@@ -0,0 +1,103 @@
/*
* 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 React from 'react';
import {
type ChatFrameworkProps,
type IMessageCallback,
type RawMessage,
type IChatFlowProps,
type FooterConfig,
type IOnImageClickEvent,
} from '@coze/chat-sdk';
import { type Layout, type DebugProps, type HeaderConfig } from './base';
export interface IWorkflow {
id?: string;
parameters?: Record<string, unknown>;
header?: Record<string, string>;
}
export interface IProject {
id: string;
type: 'app' | 'bot';
mode: 'draft' | 'release' | 'websdk' | 'audit'; // 草稿模式 | 发布模式 | webSdk发布
caller?: 'UI_BUILDER' | 'CANVAS';
connectorId?: string;
conversationName?: string; // project的话必须填写
conversationId?: string; // type 为bot的话必须填写
sectionId?: string; // type 为bot的话必须填写
name?: string;
defaultName?: string;
defaultIconUrl?: string;
iconUrl?: string;
layout?: Layout;
version?: string;
onBoarding?: {
prologue: string;
suggestions: string[];
};
}
export interface IEventCallbacks {
onMessageChanged?: () => void;
onMessageSended?: () => void;
onMessageReceivedStart?: () => void;
onMessageRecievedFinish?: () => void;
onImageClick?: IOnImageClickEvent;
onGetChatFlowExecuteId?: (id: string) => void;
onThemeChange?: (theme: 'bg-theme' | 'light') => void;
afterMessageReceivedFinish?: IMessageCallback['afterMessageReceivedFinish'];
onInitSuccess?: () => void;
}
export interface IBuilderChatProps {
workflow: IWorkflow;
project: IProject;
eventCallbacks?: IEventCallbacks;
userInfo: IChatFlowProps['userInfo'];
areaUi: {
isDisabled?: boolean; // 默认 false
uploadable?: boolean; // 默认 true
isNeedClearContext?: boolean; // 是否显示 clearContext按钮
isNeedClearMessage?: boolean; // 是否显示 clearMessage按钮
//isShowHeader?: boolean; // 默认 false
//isShowFooter?: boolean; // 默认 false
input?: {
placeholder?: string;
renderChatInputTopSlot?: (isChatError?: boolean) => React.ReactNode;
isShow?: boolean; //默认 true
defaultText?: string;
isNeedAudio?: boolean; // 是否需要语音输入默认是false
isNeedTaskMessage?: boolean;
};
header?: HeaderConfig; // 默认是
footer?: FooterConfig;
uiTheme?: 'uiBuilder' | 'chatFlow'; // uiBuilder 的主题
renderLoading?: () => React.ReactNode;
};
auth?: {
type: 'external' | 'internal'; // 内部: cookie换token 外部: internal
token?: string;
refreshToken?: () => Promise<string> | string;
};
style?: React.CSSProperties;
debug?: DebugProps;
setting?: Partial<ChatFrameworkProps['setting']>;
}
export interface BuilderChatRef {
sendMessage: (message: RawMessage) => void;
}

View File

@@ -0,0 +1,24 @@
/*
* 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 { Layout, type HeaderConfig, type DebugProps } from './base';
export type {
IWorkflow,
IProject,
IEventCallbacks,
IBuilderChatProps,
BuilderChatRef,
} from './builder-chat';

View 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' />

View File

@@ -0,0 +1,54 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"compilerOptions": {
"types": [],
"paths": {
"@/*": ["./src/*"]
},
"esModuleInterop": true,
"resolveJsonModule": true,
"rootDir": "./src",
"preserveSymlinks": false,
"skipLibCheck": true,
"outDir": "./dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["src", "src/**/*.json"],
"exclude": ["./src/**/*.test.ts", "src/**/__tests__/**", "src/test/setup.ts"],
"references": [
{
"path": "../../../arch/bot-env/tsconfig.build.json"
},
{
"path": "../../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../../arch/i18n/tsconfig.build.json"
},
{
"path": "../../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../../config/postcss-config/tsconfig.build.json"
},
{
"path": "../../../../config/stylelint-config/tsconfig.build.json"
},
{
"path": "../../../../config/tailwind-config/tsconfig.build.json"
},
{
"path": "../../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../../config/vitest-config/tsconfig.build.json"
},
{
"path": "../../../../infra/plugins/pkg-root-webpack-plugin/tsconfig.build.json"
},
{
"path": "../open-env-adapter/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
],
"exclude": ["**/*"]
}

View File

@@ -0,0 +1,31 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": [
"stories",
"vitest.config.ts",
"tailwind.config.ts",
"rspack.config.ts",
"rspack-config/**/*.ts",
"./src/**/*.test.ts",
"**/__tests__",
"src/test/setup.ts"
],
"exclude": ["node_modules", "./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"types": ["vitest/globals", "node"],
"paths": {
"@/*": ["./src/*"]
},
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "./dist"
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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',
test: {
setupFiles: ['__tests__/setup.ts'],
},
});

View File

@@ -0,0 +1,8 @@
const { defineConfig } = require('@coze-arch/stylelint-config');
module.exports = defineConfig({
extends: [],
rules: {
'order/properties-order': null,
},
});

View File

@@ -0,0 +1,75 @@
# @coze-studio/open-env-adapter
Coze Web ChatApp SDK
## Overview
This package is part of the Coze Studio monorepo and provides library functionality. It includes api, sdk.
## Getting Started
### Installation
Add this package to your `package.json`:
```json
{
"dependencies": {
"@coze-studio/open-env-adapter": "workspace:*"
}
}
```
Then run:
```bash
rush update
```
### Usage
```typescript
import { /* exported functions/components */ } from '@coze-studio/open-env-adapter';
// Example usage
// TODO: Add specific usage examples
```
## Features
- Api
- Sdk
## API Reference
### Exports
- `iframeAppHost,
cozeOfficialHost,
openApiCdnUrlByRegion,
openApiHostByRegion,
openApiHostByRegionWithToken,
openSdkPrefix,
getOpenSDKUrl,
getOpenSDKPath,
eventMeta,`
For detailed API documentation, please refer to the TypeScript definitions.
## 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

View File

@@ -0,0 +1,23 @@
/*
* 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 { getOpenSDKPath } from '@/chat';
describe('chat-env', () => {
it('getOpenSDKUrl', () => {
const sdkPath = getOpenSDKPath('1.0.0');
expect(sdkPath).toBe('');
});
});

View 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.
*/
vi.stubGlobal('IS_OVERSEA', false);

View File

@@ -0,0 +1,12 @@
{
"operationSettings": [
{
"operationName": "test:cov",
"outputFolderNames": ["coverage"]
},
{
"operationName": "ts-check",
"outputFolderNames": ["./dist"]
}
]
}

View File

@@ -0,0 +1,15 @@
const { defineConfig } = require('@coze-arch/eslint-config');
module.exports = defineConfig({
packageRoot: __dirname,
preset: 'web',
rules: {
'@coze-arch/max-line-per-function': [
'error',
{
max: 300,
},
],
'no-restricted-imports': 'off',
},
});

View File

@@ -0,0 +1,64 @@
{
"name": "@coze-studio/open-env-adapter",
"version": "0.0.1",
"description": "Coze Web ChatApp SDK ",
"license": "Apache-2.0",
"author": "gaoding.devingao@bytedance.com",
"maintainers": [
"gaoding.devingao@bytedance.com"
],
"sideEffects": false,
"exports": {
".": "./src/index.ts",
"./chat": "./src/chat/index.ts"
},
"main": "src/index.ts",
"types": "./src/index.ts",
"typesVersions": {
"*": {
"chat": [
"./src/chat/index.ts"
]
}
},
"scripts": {
"build": "exit 0",
"lint": "eslint ./ --cache",
"test": "vitest --run --passWithNoTests",
"test:cov": "vitest run --coverage"
},
"dependencies": {},
"devDependencies": {
"@coze-arch/bot-env": "workspace:*",
"@coze-arch/bot-typings": "workspace:*",
"@coze-arch/eslint-config": "workspace:*",
"@coze-arch/pkg-root-webpack-plugin": "workspace:*",
"@coze-arch/postcss-config": "workspace:*",
"@coze-arch/stylelint-config": "workspace:*",
"@coze-arch/tailwind-config": "workspace:*",
"@coze-arch/ts-config": "workspace:*",
"@coze-arch/vitest-config": "workspace:*",
"@rspack/plugin-react-refresh": "0.6.0",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@types/lodash-es": "^4.17.10",
"@types/node": "^18",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"@vitest/coverage-v8": "~3.0.5",
"concurrently": "~8.2.2",
"css-loader": "^6.10.0",
"debug": "^4.3.4",
"style-loader": "^3.3.4",
"tailwindcss": "~3.3.3",
"ts-node": "^10.9.1",
"typescript": "~5.8.2",
"vitest": "~3.0.5",
"webpack": "~5.91.0"
},
"// deps": "debug@^4.3.4 为脚本自动补齐,请勿改动",
"botPublishConfig": {
"main": "dist/index.js"
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.
*/
/**
* 依赖 treeShaking 去除无关配置(Argus)
*/
const sdkRegion = 'cn';
export const iframeAppHost = '';
export const cozeOfficialHost = '';
export const openApiCdnUrlByRegion = IS_OVERSEA
? // cp-disable-next-line
'https://sf16-sg.tiktokcdn.com/obj/eden-sg/rkzild_lgvj/ljhwZthlaukjlkulzlp/'
: // cp-disable-next-line
'https://lf3-static.bytednsdoc.com/obj/eden-cn/rkzild_lgvj/ljhwZthlaukjlkulzlp/';
// 用户需要修改此处baseurl用于开放平台接口的域名配置
export const openApiHostByRegion =
typeof location !== 'undefined' ? location.origin : 'https://api.xxx.com';
export const openApiHostByRegionWithToken = openApiHostByRegion;
export const openSdkPrefix = '';
export const getOpenSDKUrl = (_version: string) => '';
export const getOpenSDKPath = (_version: string) => '';
export const eventMeta = {
region: sdkRegion,
is_release: false,
dev: false,
};

View File

@@ -0,0 +1,27 @@
/*
* 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 {
iframeAppHost,
cozeOfficialHost,
openApiCdnUrlByRegion,
openApiHostByRegion,
openApiHostByRegionWithToken,
openSdkPrefix,
getOpenSDKUrl,
getOpenSDKPath,
eventMeta,
} from './chat';

View 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' />

View File

@@ -0,0 +1,48 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"compilerOptions": {
"types": [],
"paths": {
"@/*": ["./src/*"]
},
"esModuleInterop": true,
"resolveJsonModule": true,
"rootDir": "./src",
"preserveSymlinks": false,
"skipLibCheck": true,
"outDir": "./dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo"
},
"include": ["src", "src/**/*.json"],
"exclude": ["./src/**/*.test.ts", "src/**/__tests__/**", "src/test/setup.ts"],
"references": [
{
"path": "../../../arch/bot-env/tsconfig.build.json"
},
{
"path": "../../../arch/bot-typings/tsconfig.build.json"
},
{
"path": "../../../../config/eslint-config/tsconfig.build.json"
},
{
"path": "../../../../config/postcss-config/tsconfig.build.json"
},
{
"path": "../../../../config/stylelint-config/tsconfig.build.json"
},
{
"path": "../../../../config/tailwind-config/tsconfig.build.json"
},
{
"path": "../../../../config/ts-config/tsconfig.build.json"
},
{
"path": "../../../../config/vitest-config/tsconfig.build.json"
},
{
"path": "../../../../infra/plugins/pkg-root-webpack-plugin/tsconfig.build.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"composite": true
},
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.misc.json"
}
],
"exclude": ["**/*"]
}

View File

@@ -0,0 +1,31 @@
{
"extends": "@coze-arch/ts-config/tsconfig.web.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": [
"stories",
"vitest.config.ts",
"tailwind.config.ts",
"rspack.config.ts",
"rspack-config/**/*.ts",
"./src/**/*.test.ts",
"**/__tests__",
"src/test/setup.ts"
],
"exclude": ["node_modules", "./dist"],
"references": [
{
"path": "./tsconfig.build.json"
}
],
"compilerOptions": {
"rootDir": "./",
"types": ["vitest/globals", "node"],
"paths": {
"@/*": ["./src/*"]
},
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "./dist"
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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',
test: {
setupFiles: ['__tests__/setup.ts'],
},
});