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,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>
);