feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
.ui-kit-onboarding {
|
||||
// figma 此处为 70px 时机 dom 为 safe area 24px + 此处 46px = 70px
|
||||
margin-top: 46px;
|
||||
padding: 0 24px
|
||||
}
|
||||
|
||||
.message {
|
||||
align-self: flex-start;
|
||||
max-width: 100%;
|
||||
margin-bottom: 8px;
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
.message-selectable {
|
||||
margin-left: 29px;
|
||||
}
|
||||
|
||||
.onboarding-message-content {
|
||||
word-break: break-word;
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function */
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import classNames from 'classnames';
|
||||
import { useDebounce, useSize, useUpdateEffect } from 'ahooks';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { useReportTti } from '@coze-arch/report-tti';
|
||||
import {
|
||||
BotMode,
|
||||
SuggestedQuestionsShowMode,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { useChatBackgroundState } from '@coze-studio/bot-detail-store';
|
||||
import {
|
||||
CozeImageWithPreview,
|
||||
CozeLink,
|
||||
LazyCozeMdBox,
|
||||
MessageBox,
|
||||
NO_MESSAGE_ID_MARK,
|
||||
OnBoarding as UIKitOnBoarding,
|
||||
} from '@coze-common/chat-uikit';
|
||||
import {
|
||||
type ComponentTypesMap,
|
||||
useOnboardingCenterOffset,
|
||||
} from '@coze-common/chat-area';
|
||||
import {
|
||||
ONBOARDING_PREVIEW_DELAY,
|
||||
OnboardingVariable,
|
||||
useRenderVariable,
|
||||
} from '@coze-agent-ide/onboarding';
|
||||
|
||||
import s from './index.module.less';
|
||||
/**
|
||||
* 当有无开场白的状态变化时, 会依次一次 reflow 同时高度发生变化, useSize 监听变化后, marginTop 改变触发一次 repaint, 导致会有两次渲染, 产生抖动
|
||||
* 现在需要调和这两次渲染
|
||||
* 需要优化 onboarding dom 结构, 使得 onboarding 的居中能力是 css 实现的, 解决这个问题
|
||||
* not-needed
|
||||
* |
|
||||
* |
|
||||
* ready-empty-onboarding <--+--> ready-onboarding
|
||||
* | |
|
||||
* | v
|
||||
* harmonized-empty-onboarding harmonized-onboarding
|
||||
* | |
|
||||
* v |
|
||||
* not-needed <------------------------+
|
||||
*/
|
||||
type HarmonizeOnboardingRender =
|
||||
| 'not-needed'
|
||||
| 'ready-empty-onboarding'
|
||||
| 'ready-onboarding'
|
||||
| 'harmonized-onboarding'
|
||||
| 'harmonized-empty-onboarding';
|
||||
|
||||
const getHarmonizedOnboardingHeight = ({
|
||||
onboardingHeight = 0,
|
||||
status,
|
||||
}: {
|
||||
onboardingHeight?: number;
|
||||
status: HarmonizeOnboardingRender;
|
||||
}) => {
|
||||
const emptyOnboardingHeight = 56;
|
||||
const contentOnboardingHeight = 118;
|
||||
switch (status) {
|
||||
case 'not-needed':
|
||||
return onboardingHeight;
|
||||
case 'ready-onboarding':
|
||||
case 'harmonized-onboarding':
|
||||
return contentOnboardingHeight;
|
||||
default:
|
||||
return emptyOnboardingHeight;
|
||||
}
|
||||
};
|
||||
|
||||
export const OnboardingMessagePop: ComponentTypesMap['onboarding'] = ({
|
||||
prologue,
|
||||
suggestions,
|
||||
sendTextMessage,
|
||||
hasMessages,
|
||||
enableImageAutoSize,
|
||||
imageAutoSizeContainerWidth,
|
||||
showBackground: showBackgroundProp,
|
||||
eventCallbacks,
|
||||
}) => {
|
||||
const [harmonizeRenders, setHarmonizeRenders] =
|
||||
useState<HarmonizeOnboardingRender>('not-needed');
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const onboardingSize = useSize(ref);
|
||||
const {
|
||||
avatar,
|
||||
name,
|
||||
mode,
|
||||
id = '',
|
||||
} = useBotInfoStore(
|
||||
useShallow(state => ({
|
||||
mode: state.mode,
|
||||
avatar: state.icon_url,
|
||||
name: state.name,
|
||||
id: state.botId,
|
||||
})),
|
||||
);
|
||||
const { onBoardingSuggestionWrap } = useBotSkillStore(
|
||||
useShallow(state => ({
|
||||
onBoardingSuggestionWrap:
|
||||
state.onboardingContent.suggested_questions_show_mode ===
|
||||
SuggestedQuestionsShowMode.All,
|
||||
})),
|
||||
);
|
||||
const debouncedPrologue = useDebounce(prologue, {
|
||||
wait: ONBOARDING_PREVIEW_DELAY,
|
||||
});
|
||||
const userInfo = userStoreService.useUserInfo();
|
||||
const { showBackground: showBackgroundFromChatBackgroundState } =
|
||||
useChatBackgroundState();
|
||||
const showBackground =
|
||||
showBackgroundProp ?? showBackgroundFromChatBackgroundState;
|
||||
|
||||
const renderVariable = useRenderVariable({
|
||||
[OnboardingVariable.USER_NAME]: userInfo?.name ?? '',
|
||||
});
|
||||
|
||||
// TTI
|
||||
useReportTti({
|
||||
isLive: mode === BotMode.SingleMode,
|
||||
extra: {
|
||||
mode: 'single-agent',
|
||||
},
|
||||
});
|
||||
const harmonizedHeight = getHarmonizedOnboardingHeight({
|
||||
onboardingHeight: onboardingSize?.height,
|
||||
status: harmonizeRenders,
|
||||
});
|
||||
const marginTop = useOnboardingCenterOffset({
|
||||
onboardingHeight: harmonizedHeight,
|
||||
});
|
||||
|
||||
const isOnboardingEmpty = !debouncedPrologue && !suggestions.length;
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (isOnboardingEmpty) {
|
||||
setHarmonizeRenders('ready-empty-onboarding');
|
||||
return;
|
||||
}
|
||||
setHarmonizeRenders('ready-onboarding');
|
||||
}, [isOnboardingEmpty]);
|
||||
|
||||
useEffect(() => {
|
||||
if (harmonizeRenders === 'not-needed') {
|
||||
return;
|
||||
}
|
||||
if (harmonizeRenders === 'ready-onboarding') {
|
||||
setHarmonizeRenders('harmonized-onboarding');
|
||||
return;
|
||||
}
|
||||
if (harmonizeRenders === 'ready-empty-onboarding') {
|
||||
setHarmonizeRenders('harmonized-empty-onboarding');
|
||||
return;
|
||||
}
|
||||
setHarmonizeRenders('not-needed');
|
||||
}, [onboardingSize?.height]);
|
||||
|
||||
if (hasMessages && !prologue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hasMessages && prologue) {
|
||||
return (
|
||||
<div className={classNames(s.message)}>
|
||||
<MessageBox
|
||||
messageId={null}
|
||||
senderInfo={{ url: avatar, nickname: name, id }}
|
||||
showUserInfo={true}
|
||||
theme="grey"
|
||||
getBotInfo={() => undefined}
|
||||
showBackground={showBackground}
|
||||
enableImageAutoSize={enableImageAutoSize}
|
||||
imageAutoSizeContainerWidth={imageAutoSizeContainerWidth}
|
||||
eventCallbacks={eventCallbacks}
|
||||
>
|
||||
<div
|
||||
className={s['onboarding-message-content']}
|
||||
data-grab-mark={NO_MESSAGE_ID_MARK}
|
||||
>
|
||||
<LazyCozeMdBox
|
||||
insertedElements={renderVariable(prologue)}
|
||||
markDown={prologue}
|
||||
autoFixSyntax={{ autoFixEnding: false }}
|
||||
slots={{
|
||||
Image: CozeImageWithPreview,
|
||||
Link: CozeLink,
|
||||
}}
|
||||
></LazyCozeMdBox>
|
||||
</div>
|
||||
</MessageBox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<UIKitOnBoarding
|
||||
ref={ref}
|
||||
style={{ marginTop }}
|
||||
className={s['ui-kit-onboarding']}
|
||||
name={name}
|
||||
avatar={avatar}
|
||||
prologue={debouncedPrologue}
|
||||
suggestionListWithString={suggestions.map(sug => sug.content)}
|
||||
onSuggestionClick={sendTextMessage}
|
||||
showBackground={showBackground}
|
||||
suggestionsWithStringWrap={onBoardingSuggestionWrap}
|
||||
mdBoxProps={{
|
||||
insertedElements: renderVariable(prologue),
|
||||
slots: {
|
||||
Image: CozeImageWithPreview,
|
||||
Link: CozeLink,
|
||||
},
|
||||
}}
|
||||
enableAutoSizeImage={enableImageAutoSize}
|
||||
imageAutoSizeContainerWidth={imageAutoSizeContainerWidth}
|
||||
eventCallbacks={eventCallbacks}
|
||||
suggestionItemColor="white"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
.more-btn-tooltip {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.3px;
|
||||
|
||||
.tool-text {
|
||||
cursor: pointer;
|
||||
color: var(--light-color-brand-brand-3, #9197f1);
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { OpenModalEvent, emitEvent } from '@coze-arch/bot-utils';
|
||||
import { BotMode } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const UploadTooltipsContent = () => {
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
|
||||
const mode = useBotInfoStore(state => state.mode);
|
||||
const isMulti = mode === BotMode.MultiMode;
|
||||
const isWorkflow = mode === BotMode.WorkflowMode;
|
||||
|
||||
const botPreviewAttachI18nKey = 'bot_preview_attach_0319';
|
||||
|
||||
const addApi = () => {
|
||||
if (isReadonly) {
|
||||
return;
|
||||
}
|
||||
emitEvent(OpenModalEvent.PLUGIN_API_MODAL_OPEN, { type: 1 });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={s['more-btn-tooltip']} onClick={e => e.stopPropagation()}>
|
||||
{I18n.t(botPreviewAttachI18nKey, {
|
||||
placeholder:
|
||||
isMulti || isWorkflow ? (
|
||||
I18n.t('bot_preview_attach_select')
|
||||
) : (
|
||||
<span className={classnames(s['tool-text'])} onClick={addApi}>
|
||||
{I18n.t('bot_preview_attach_select')}
|
||||
</span>
|
||||
),
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user