feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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 React, { type ComponentProps, Suspense, forwardRef, lazy } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
type BotSpace,
|
||||
SpaceType,
|
||||
type DraftBot,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { type UploadValue } from '@coze-common/biz-components/picture-upload';
|
||||
import { IconTeamDefault } from '@coze-arch/bot-icons';
|
||||
import { botInputLengthService } from '@coze-agent-ide/bot-input-length-limit';
|
||||
import {
|
||||
FormTextArea,
|
||||
FormInput,
|
||||
Tag,
|
||||
Form,
|
||||
FormSelect,
|
||||
Avatar,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { FormSwitch } from './form-switch';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const LazyReactMarkdown = lazy(() => import('react-markdown'));
|
||||
|
||||
const ReactMarkdown = (props: ComponentProps<typeof LazyReactMarkdown>) => (
|
||||
<Suspense fallback={null}>
|
||||
<LazyReactMarkdown {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
export type AgentInfoFormValue = Partial<{
|
||||
bot_uri: UploadValue;
|
||||
name: string;
|
||||
target: string;
|
||||
spaceId?: string;
|
||||
enableMonetize?: boolean;
|
||||
}>;
|
||||
|
||||
export interface AgentInfoFormProps {
|
||||
className?: string;
|
||||
mode: 'add' | 'update';
|
||||
showSpace: boolean;
|
||||
initialValues: Partial<DraftBot>;
|
||||
spacesList: BotSpace[];
|
||||
currentSpaceId?: string; // Current space ID from store
|
||||
hideOperation?: boolean; // hide_operation from store
|
||||
checkErr: boolean;
|
||||
errMsg: string;
|
||||
onValuesChange: (values: AgentInfoFormValue) => void;
|
||||
slot?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const AgentInfoForm = forwardRef<
|
||||
Form<AgentInfoFormValue>,
|
||||
AgentInfoFormProps
|
||||
>(
|
||||
// eslint-disable-next-line complexity
|
||||
(
|
||||
{
|
||||
className,
|
||||
mode,
|
||||
showSpace,
|
||||
initialValues,
|
||||
spacesList,
|
||||
currentSpaceId,
|
||||
hideOperation,
|
||||
checkErr,
|
||||
errMsg,
|
||||
onValuesChange,
|
||||
slot,
|
||||
},
|
||||
ref,
|
||||
) => (
|
||||
<Form<AgentInfoFormValue>
|
||||
ref={ref}
|
||||
showValidateIcon={false}
|
||||
className={classNames(s['upload-form'], className)} // Ensure class name is correct
|
||||
onValueChange={values => {
|
||||
onValuesChange(values);
|
||||
}}
|
||||
>
|
||||
<FormInput
|
||||
initValue={botInputLengthService.sliceStringByMaxLength({
|
||||
value: initialValues?.name ?? '',
|
||||
field: 'botName',
|
||||
})}
|
||||
field="name"
|
||||
label={I18n.t('bot_create_name')}
|
||||
noErrorMessage
|
||||
maxLength={botInputLengthService.getInputLengthLimit('botName')}
|
||||
rules={[{ required: true }]}
|
||||
placeholder={I18n.t('bot_create_name_placeholder')}
|
||||
getValueLength={reactText =>
|
||||
botInputLengthService.getValueLength(reactText)
|
||||
}
|
||||
/>
|
||||
{IS_OVERSEA && mode === 'add' ? (
|
||||
<FormSwitch
|
||||
field="enableMonetize"
|
||||
label={I18n.t('monetization')}
|
||||
desc={I18n.t('monetization_des')}
|
||||
initValue={true} // Consider if initial value should be prop
|
||||
rules={[{ required: true }]}
|
||||
/>
|
||||
) : null}
|
||||
<FormTextArea
|
||||
field="target"
|
||||
initValue={botInputLengthService.sliceStringByMaxLength({
|
||||
value: initialValues?.description ?? '',
|
||||
field: 'botDescription',
|
||||
})}
|
||||
label={I18n.t('bot_create_desciption')}
|
||||
placeholder={I18n.t('bot_create_description_placeholder')}
|
||||
maxCount={botInputLengthService.getInputLengthLimit('botDescription')}
|
||||
maxLength={botInputLengthService.getInputLengthLimit('botDescription')}
|
||||
getValueLength={botInputLengthService.getValueLength}
|
||||
/>
|
||||
{showSpace && mode === 'add' ? (
|
||||
<FormSelect
|
||||
label={I18n.t('duplicate_select_workspace')}
|
||||
field="spaceId"
|
||||
initValue={
|
||||
hideOperation
|
||||
? spacesList?.[0]?.id
|
||||
: (currentSpaceId ?? spacesList?.[0]?.id)
|
||||
}
|
||||
placeholder={I18n.t('select_team')}
|
||||
noErrorMessage
|
||||
className={classNames(s.select)}
|
||||
rules={[{ required: true }]}
|
||||
renderSelectedItem={(optionNode: BotSpace) => (
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Avatar
|
||||
src={optionNode.icon_url}
|
||||
size="extra-extra-small"
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
{optionNode.name}
|
||||
</Avatar>
|
||||
<span className={classNames(s['select-name'])}>
|
||||
{optionNode.name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{spacesList
|
||||
?.filter(t => !t.hide_operation)
|
||||
?.map(item => (
|
||||
<FormSelect.Option value={item.id} {...item} key={item.id}>
|
||||
<div className="ml-[8px]">
|
||||
{item.icon_url ? (
|
||||
<Avatar size="extra-small" src={item.icon_url} />
|
||||
) : (
|
||||
<IconTeamDefault
|
||||
className={classNames(s['select-item-icon'])}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={classNames(s['select-item-name'])}>
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: false,
|
||||
}}
|
||||
style={{
|
||||
maxWidth: '280px',
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</div>
|
||||
{item.space_type === SpaceType.Team && (
|
||||
<Tag color="brand">{I18n.t('develop_team_team')}</Tag>
|
||||
)}
|
||||
</FormSelect.Option>
|
||||
))}
|
||||
</FormSelect>
|
||||
) : null}
|
||||
{slot}
|
||||
{checkErr ? (
|
||||
<div className={s['content-check-error']}>
|
||||
<ReactMarkdown
|
||||
skipHtml={true}
|
||||
className={s.markdown}
|
||||
linkTarget="_blank"
|
||||
>
|
||||
{errMsg ?? I18n.t('publish_audit_pop7')}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
) : null}
|
||||
</Form>
|
||||
),
|
||||
);
|
||||
@@ -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 cls from 'classnames';
|
||||
import { Switch, withField, type SwitchProps } from '@coze-arch/coze-design';
|
||||
|
||||
function SwitchWithDesc({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
desc,
|
||||
descClassName,
|
||||
switchClassName,
|
||||
...rest
|
||||
}: Omit<SwitchProps, 'checked'> & {
|
||||
value?: boolean;
|
||||
desc: string;
|
||||
descClassName?: string;
|
||||
switchClassName?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cls('flex items-center justify-between', className)}>
|
||||
<span className={cls('coz-fg-primary', descClassName)}>{desc}</span>
|
||||
<Switch
|
||||
size="small"
|
||||
{...rest}
|
||||
checked={value}
|
||||
onChange={onChange}
|
||||
className={cls('shrink-0', switchClassName)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FormSwitch = withField(SwitchWithDesc);
|
||||
@@ -0,0 +1,408 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
.card-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
min-width: 323px;
|
||||
height: 180px;
|
||||
padding: 12px 16px 16px 20px;
|
||||
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 6px 8px 0 rgb(28 31 35 / 6%);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 10px 12px 0 rgb(28 31 35 / 10%);
|
||||
}
|
||||
|
||||
// 更多操作按钮active时,不触发父级active
|
||||
&:active:not(:focus-within) {
|
||||
background-color: #e6e7ea;
|
||||
}
|
||||
|
||||
.bot-info {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
margin-bottom: auto;
|
||||
|
||||
.bot-avatar {
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
margin-top: 12px;
|
||||
margin-left: 4px;
|
||||
|
||||
background-color: #2E2E3814;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.bot-text {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
margin-left: 16px;
|
||||
|
||||
.bot-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.bot-name {
|
||||
margin-top: 12px;
|
||||
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: #1c1d23;
|
||||
}
|
||||
}
|
||||
|
||||
.bot-description {
|
||||
height: 40px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: rgb(28 29 35 / 80%);
|
||||
}
|
||||
|
||||
.bot-hot-and-category {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.bot-hot-value {
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #1C1D2359;
|
||||
}
|
||||
|
||||
.bot-category-tag {
|
||||
margin-left: 16px;
|
||||
border: 1px solid #1D1C2314;
|
||||
border-radius: 6px;
|
||||
|
||||
.bot-category-text {
|
||||
font-size: 12px;
|
||||
color: #6B6B75;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-model-and-publish {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.bot-publish-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 60%);
|
||||
letter-spacing: 0.12px;
|
||||
|
||||
&-icon {
|
||||
margin-right: 4px;
|
||||
color: rgb(62 194 84);
|
||||
}
|
||||
|
||||
&-warning-icon {
|
||||
margin-right: 4px;
|
||||
color: var(--semi-color-warning);
|
||||
}
|
||||
}
|
||||
|
||||
.bot-model {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 35%);
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: calc(100% - 4px);
|
||||
}
|
||||
|
||||
.bot-publish-platform {
|
||||
display: flex;
|
||||
column-gap: 6px;
|
||||
align-items: center;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 60%);
|
||||
letter-spacing: 0.12px;
|
||||
|
||||
:global {
|
||||
.semi-avatar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-creator-info {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
padding: 0 4px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 35%);
|
||||
letter-spacing: 0.12px;
|
||||
|
||||
.creator-avatar {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.bot-creator {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 29 35 / 35%);
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.bot-edit-time {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explore-height {
|
||||
height: 136px;
|
||||
}
|
||||
|
||||
.publish-popover {
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid #efefef;
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
:global {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bot-description-tooltip {
|
||||
color: var(--semi-color-bg-0);
|
||||
}
|
||||
|
||||
.add-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.add-card-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.name-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 16px 16px 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 48px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
max-width: calc(100% - 156px);
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: #000;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.extra {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.description {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #494c4f;
|
||||
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.recent-modify {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgb(28 31 35 / 60%);
|
||||
}
|
||||
|
||||
.creator {
|
||||
width: fit-content;
|
||||
padding: 4px;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #346ef8;
|
||||
|
||||
background: rgb(51 112 255 / 10%);
|
||||
border: none !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
.upload-form {
|
||||
.upload-filed {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.textarea-multi-line {
|
||||
:global {
|
||||
.semi-input-textarea {
|
||||
padding-right: 66px;
|
||||
}
|
||||
|
||||
.semi-input-textarea-counter {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
bottom: 8px;
|
||||
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
|
||||
color: var(--light-usage-text-color-text-3, rgb(28 29 35 / 35%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-form-field-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-form-item {
|
||||
:global {
|
||||
.semi-form-field-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.semi-form-field-label-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.semi-form-field {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collect-num {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 4px;
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.content-check-error {
|
||||
margin-top: -8px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.bot-ui-modal {
|
||||
:global {
|
||||
.semi-modal-content .semi-modal-body {
|
||||
padding: 12px 0 22px;
|
||||
}
|
||||
|
||||
.semi-form-vertical .semi-form-field {
|
||||
padding-bottom: 14px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
width: 100%;
|
||||
|
||||
.select-name {
|
||||
margin-left: 6px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.select-item-icon {
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-item-name {
|
||||
margin-right: 16px;
|
||||
margin-left: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
useAgentFormManagement,
|
||||
type UseAgentFormManagementProps,
|
||||
} from './use-agent-form-management';
|
||||
export {
|
||||
useAgentPersistence,
|
||||
type UseAgentPersistenceProps,
|
||||
} from './use-agent-persistence';
|
||||
export {
|
||||
AgentInfoForm,
|
||||
type AgentInfoFormProps,
|
||||
type AgentInfoFormValue,
|
||||
} from './agent-info-form';
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState, useRef } from 'react';
|
||||
|
||||
import { type Form } from '@coze-arch/coze-design';
|
||||
import { type DraftBot } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { type AgentInfoFormValue } from './agent-info-form';
|
||||
|
||||
export interface UseAgentFormManagementProps {
|
||||
initialBotInfo?: DraftBot;
|
||||
}
|
||||
|
||||
export const useAgentFormManagement = ({
|
||||
initialBotInfo,
|
||||
}: UseAgentFormManagementProps) => {
|
||||
const formRef = useRef<Form<AgentInfoFormValue>>(null);
|
||||
const [isOkButtonDisable, setOkButtonDisable] = useState(
|
||||
!initialBotInfo?.name?.trim(),
|
||||
);
|
||||
const [botInfo4Generate, setBotInfo4Generate] = useState<{
|
||||
name: string;
|
||||
desc: string;
|
||||
avatar: { uri: string; url: string };
|
||||
}>({
|
||||
name: initialBotInfo?.name || '',
|
||||
desc: initialBotInfo?.description || '',
|
||||
avatar: {
|
||||
uri: initialBotInfo?.icon_uri || '',
|
||||
url: initialBotInfo?.icon_url || '',
|
||||
},
|
||||
});
|
||||
const [checkErr, setCheckErr] = useState(false);
|
||||
const [errMsg, setErrMsg] = useState('');
|
||||
const [confirmDisabled, setConfirmDisabled] = useState(false);
|
||||
|
||||
const resetFormState = () => {
|
||||
setOkButtonDisable(!initialBotInfo?.name?.trim());
|
||||
setBotInfo4Generate({
|
||||
name: initialBotInfo?.name || '',
|
||||
desc: initialBotInfo?.description || '',
|
||||
avatar: {
|
||||
uri: initialBotInfo?.icon_uri || '',
|
||||
url: initialBotInfo?.icon_url || '',
|
||||
},
|
||||
});
|
||||
setCheckErr(false);
|
||||
setErrMsg('');
|
||||
};
|
||||
|
||||
const handleFormValuesChange = (values: AgentInfoFormValue) => {
|
||||
setBotInfo4Generate({
|
||||
name: values.name?.trim() || '',
|
||||
desc: values.target?.trim() || '',
|
||||
avatar: {
|
||||
uri: values.bot_uri?.[0]?.uid || '',
|
||||
url: values.bot_uri?.[0]?.url || '',
|
||||
},
|
||||
});
|
||||
setCheckErr(false);
|
||||
setErrMsg('');
|
||||
setOkButtonDisable(!values.name?.trim());
|
||||
};
|
||||
|
||||
const getValues = async () => {
|
||||
const formApi = formRef.current?.formApi;
|
||||
await formApi?.validate();
|
||||
return formApi?.getValues();
|
||||
};
|
||||
|
||||
const setBotIcon = (val: { url: string; uid: string }) => {
|
||||
const formApi = formRef.current?.formApi;
|
||||
formApi?.setValue('bot_uri', [val]);
|
||||
};
|
||||
|
||||
return {
|
||||
formRef,
|
||||
isOkButtonDisable,
|
||||
botInfo4Generate,
|
||||
checkErr,
|
||||
errMsg,
|
||||
confirmDisabled,
|
||||
setCheckErr,
|
||||
setErrMsg,
|
||||
setConfirmDisabled,
|
||||
setOkButtonDisable,
|
||||
handleFormValuesChange,
|
||||
getValues,
|
||||
setBotIcon,
|
||||
resetFormState,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable complexity */
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { useCollaborationStore } from '@coze-studio/bot-detail-store/collaboration';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { updateBotRequest } from '@coze-studio/bot-detail-store';
|
||||
import {
|
||||
REPORT_EVENTS as ReportEventNames,
|
||||
createReportEvent,
|
||||
} from '@coze-arch/report-events';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Toast } from '@coze-arch/coze-design';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import {
|
||||
type BotSpace,
|
||||
SpaceType,
|
||||
type DraftBotCreateResponse,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type AgentInfoFormValue } from './agent-info-form';
|
||||
|
||||
type OnSuccessCallback = (
|
||||
botId?: string,
|
||||
spaceId?: string,
|
||||
extra?: { botName?: string; botAvatar?: string; botDesc?: string },
|
||||
) => void | Promise<void>;
|
||||
|
||||
export interface UseAgentPersistenceProps {
|
||||
mode: 'add' | 'update';
|
||||
botId?: string; // Current bot ID for update mode
|
||||
currentSpaceId?: string; // Current space ID from store
|
||||
outerSpaceId?: string; // Space ID passed via props
|
||||
getValues: () => Promise<AgentInfoFormValue | undefined>;
|
||||
onSuccess?: OnSuccessCallback;
|
||||
onError?: () => void;
|
||||
onBefore?: () => void;
|
||||
setVisible: (visible: boolean) => void;
|
||||
setCheckErr: (isError: boolean) => void;
|
||||
setErrMsg: (message: string) => void;
|
||||
bizCreateFrom?: 'navi' | 'space';
|
||||
showSpace?: boolean;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function, @coze-arch/max-line-per-function
|
||||
export const useAgentPersistence = ({
|
||||
mode,
|
||||
botId,
|
||||
currentSpaceId,
|
||||
outerSpaceId,
|
||||
getValues,
|
||||
onSuccess,
|
||||
onError,
|
||||
onBefore,
|
||||
setVisible,
|
||||
setCheckErr,
|
||||
setErrMsg,
|
||||
bizCreateFrom,
|
||||
showSpace,
|
||||
}: UseAgentPersistenceProps) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const setBotInfoByImmer = useBotInfoStore(state => state.setBotInfoByImmer);
|
||||
const setCollaborationByImmer = useCollaborationStore(
|
||||
state => state.setCollaborationByImmer,
|
||||
);
|
||||
const setPageRuntimeByImmer = usePageRuntimeStore(
|
||||
state => state.setPageRuntimeByImmer,
|
||||
);
|
||||
const {
|
||||
spaces: { bot_space_list: list },
|
||||
} = useSpaceStore();
|
||||
|
||||
const reportEvent = createReportEvent({
|
||||
eventName:
|
||||
mode === 'add' ? ReportEventNames.createBot : ReportEventNames.updateBot,
|
||||
});
|
||||
|
||||
const reportTea = ({
|
||||
resp,
|
||||
values,
|
||||
personalSpaceInfo,
|
||||
paramsSpaceId,
|
||||
}: {
|
||||
resp: DraftBotCreateResponse;
|
||||
values: AgentInfoFormValue | undefined;
|
||||
personalSpaceInfo: BotSpace | undefined;
|
||||
paramsSpaceId: string;
|
||||
}) => {
|
||||
if (resp.code === 0) {
|
||||
sendTeaEvent(EVENT_NAMES.create_bot_result, {
|
||||
source: showSpace ? 'menu_bar' : 'bot_list',
|
||||
workspace_type:
|
||||
personalSpaceInfo?.id === paramsSpaceId
|
||||
? 'personal_workspace'
|
||||
: 'team_workspace',
|
||||
result: 'success',
|
||||
bot_name: values?.name || '',
|
||||
bot_desc: values?.target || '',
|
||||
});
|
||||
} else {
|
||||
sendTeaEvent(EVENT_NAMES.create_bot_result, {
|
||||
source: showSpace ? 'menu_bar' : 'bot_list',
|
||||
workspace_type:
|
||||
personalSpaceInfo?.id === paramsSpaceId
|
||||
? 'personal_workspace'
|
||||
: 'team_workspace',
|
||||
result: 'failed',
|
||||
error_code: resp.code,
|
||||
error_message: resp.msg,
|
||||
bot_name: values?.name || '',
|
||||
bot_desc: values?.target || '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateBot = async () => {
|
||||
if (!botId) {
|
||||
const msg = I18n.t('bot_copy_id_error');
|
||||
throw new CustomError(ReportEventNames.updateBot, msg);
|
||||
}
|
||||
const values = await getValues();
|
||||
logger.info({ message: 'update values', meta: { values } });
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const botBaseInfo = {
|
||||
icon_uri: values?.bot_uri?.[0].uid || '',
|
||||
name: values?.name,
|
||||
description: values?.target ? values.target : '',
|
||||
};
|
||||
const { data } = await updateBotRequest(botBaseInfo);
|
||||
|
||||
if (data.check_not_pass) {
|
||||
setCheckErr(true);
|
||||
setErrMsg(data.check_not_pass_msg);
|
||||
onError?.();
|
||||
return;
|
||||
}
|
||||
setBotInfoByImmer(store => {
|
||||
store.icon_uri = values?.bot_uri?.[0].uid;
|
||||
store.icon_url = values?.bot_uri?.[0].url;
|
||||
store.name = values?.name;
|
||||
store.description = values?.target;
|
||||
});
|
||||
setCollaborationByImmer(store => {
|
||||
store.sameWithOnline = data.same_with_online ?? false;
|
||||
});
|
||||
setPageRuntimeByImmer(store => {
|
||||
store.hasUnpublishChange = data.has_change ?? false;
|
||||
});
|
||||
await onSuccess?.(botId, currentSpaceId, {
|
||||
botAvatar: values?.bot_uri?.[0].url,
|
||||
botName: values?.name,
|
||||
botDesc: values?.target,
|
||||
});
|
||||
|
||||
setVisible(false);
|
||||
|
||||
reportEvent.success();
|
||||
Toast.success({
|
||||
content: I18n.t('Update_success'),
|
||||
showClose: false,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
reportEvent.error({ error: e, reason: e.message });
|
||||
}
|
||||
onError?.();
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('Update_failed')),
|
||||
showClose: false,
|
||||
});
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateBot = async () => {
|
||||
const values = await getValues();
|
||||
setLoading(true);
|
||||
const paramsSpaceId =
|
||||
values?.spaceId || outerSpaceId || currentSpaceId || list?.[0]?.id || '';
|
||||
const personalSpaceInfo = list?.find(
|
||||
item => item.space_type === SpaceType.Personal,
|
||||
);
|
||||
try {
|
||||
onBefore?.();
|
||||
const resp = await DeveloperApi.DraftBotCreate({
|
||||
name: values?.name,
|
||||
description: values?.target,
|
||||
icon_uri: values?.bot_uri?.[0]?.uid,
|
||||
space_id: paramsSpaceId,
|
||||
...(IS_OVERSEA && {
|
||||
monetization_conf: { is_enable: values?.enableMonetize },
|
||||
}),
|
||||
create_from: bizCreateFrom,
|
||||
});
|
||||
if (resp.data.check_not_pass) {
|
||||
setCheckErr(true);
|
||||
setErrMsg(resp.data.check_not_pass_msg);
|
||||
onError?.();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.success({
|
||||
content: I18n.t('bot_created_toast'),
|
||||
showClose: false,
|
||||
});
|
||||
// 兼容 onSuccess 回调为同步函数的场景
|
||||
await onSuccess?.(resp.data?.bot_id, paramsSpaceId, {
|
||||
botName: values?.name,
|
||||
botDesc: values?.target,
|
||||
botAvatar: values?.bot_uri?.[0]?.url,
|
||||
});
|
||||
sendTeaEvent(EVENT_NAMES.click_create_bot_confirm, {
|
||||
click: 'success',
|
||||
bot_id: resp.data?.bot_id,
|
||||
create_type: 'create',
|
||||
});
|
||||
reportTea({ resp, values, personalSpaceInfo, paramsSpaceId });
|
||||
reportEvent.success();
|
||||
setVisible(false);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('Create_failed')),
|
||||
showClose: false,
|
||||
});
|
||||
if (e instanceof Error) {
|
||||
reportEvent.error({ error: e, reason: e.message });
|
||||
sendTeaEvent(EVENT_NAMES.click_create_bot_confirm, {
|
||||
click: 'failed',
|
||||
create_type: 'create',
|
||||
error_message: e.message,
|
||||
});
|
||||
}
|
||||
onError?.();
|
||||
// 阻止弹窗关闭
|
||||
throw e;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
loading,
|
||||
handleCreateBot,
|
||||
handleUpdateBot,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user