feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
padding: 4px;
|
||||
|
||||
|
||||
.left-content {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 PropsWithChildren } from 'react';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface ActionBarContainerProps {
|
||||
leftContent?: React.ReactNode;
|
||||
rightContent?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ActionBarContainer: React.FC<
|
||||
PropsWithChildren<ActionBarContainerProps>
|
||||
> = ({ leftContent, rightContent, children }) => (
|
||||
<div className={s.container}>
|
||||
<div className={s['icon-container']}>
|
||||
<div
|
||||
data-testid="chat-area.answer-action.left-content"
|
||||
className={s['left-content']}
|
||||
>
|
||||
{leftContent}
|
||||
</div>
|
||||
<div
|
||||
data-testid="chat-area.answer-action.right-content"
|
||||
className={s['right-content']}
|
||||
>
|
||||
{rightContent}
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
ActionBarContainer.displayName = 'ActionBarContainer';
|
||||
@@ -0,0 +1,14 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding: 4px;
|
||||
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 10%);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
// TODO 后续迭代扩展时props可细化
|
||||
interface ActionBarHoverContainerProps {
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const ActionBarHoverContainer: React.FC<
|
||||
PropsWithChildren<ActionBarHoverContainerProps>
|
||||
> = ({ children, style }) => (
|
||||
<div
|
||||
data-testid="chat-area.answer-action.hover-action-bar"
|
||||
className={classNames(s.container, ['coz-stroke-primary', 'coz-bg-max'])}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,15 @@
|
||||
@keyframes trigger-config-button-slider-in {
|
||||
from {
|
||||
transform: translateX(30px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.slide-in {
|
||||
animation: trigger-config-button-slider-in 0.5s linear;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useLatestSectionId,
|
||||
useMessageBoxContext,
|
||||
} from '@coze-common/chat-area';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
import { TriggerEnabled } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { getShowBotTriggerButton } from '../../utils/get-show-bot-trigger-button';
|
||||
import { useUpdateHomeTriggerConfig } from '../../hooks/use-update-home-trigger-config';
|
||||
import { useGetBotParticipantInfo } from '../../hooks/use-get-bot-participant-info';
|
||||
import { useAnswerActionStore } from '../../context/store';
|
||||
import { useAnswerActionPreference } from '../../context/preference';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface BotTriggerConfigButtonGroupProps {
|
||||
addonBefore?: ReactNode;
|
||||
}
|
||||
|
||||
export const BotTriggerConfigButtonGroup: React.FC<
|
||||
BotTriggerConfigButtonGroupProps
|
||||
> = ({ addonBefore }) => {
|
||||
const { message, meta } = useMessageBoxContext();
|
||||
|
||||
const { sender_id } = message;
|
||||
|
||||
const { useFavoriteBotTriggerConfigStore } = useAnswerActionStore();
|
||||
|
||||
const botParticipantInfo = useFavoriteBotTriggerConfigStore(
|
||||
state => state.favoriteBotTriggerConfigMap[sender_id ?? ''],
|
||||
);
|
||||
|
||||
const latestSectionId = useLatestSectionId();
|
||||
|
||||
const isShowTriggerButton = getShowBotTriggerButton({
|
||||
message,
|
||||
meta,
|
||||
latestSectionId,
|
||||
});
|
||||
|
||||
const { enableBotTriggerControl } = useAnswerActionPreference();
|
||||
|
||||
useGetBotParticipantInfo({
|
||||
botId: sender_id,
|
||||
isEnabled:
|
||||
isShowTriggerButton && !botParticipantInfo && enableBotTriggerControl,
|
||||
});
|
||||
|
||||
const { keepReceiveHomeTrigger, stopReceiveHomeTrigger, loading } =
|
||||
useUpdateHomeTriggerConfig({
|
||||
botId: sender_id,
|
||||
onSuccess: isKeepReceiveTrigger => {
|
||||
if (!sender_id) {
|
||||
return;
|
||||
}
|
||||
const { updateFavoriteBotTriggerConfigMapByImmer } =
|
||||
useFavoriteBotTriggerConfigStore.getState();
|
||||
updateFavoriteBotTriggerConfigMapByImmer(draft => {
|
||||
const targetConfig = draft[sender_id];
|
||||
if (!targetConfig) {
|
||||
return;
|
||||
}
|
||||
targetConfig.trigger_enabled = isKeepReceiveTrigger
|
||||
? TriggerEnabled.Open
|
||||
: TriggerEnabled.Close;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
if (!isShowTriggerButton) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!botParticipantInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { is_store_favorite, trigger_enabled } = botParticipantInfo;
|
||||
|
||||
if (!enableBotTriggerControl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_store_favorite || trigger_enabled !== TriggerEnabled.Init) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
styles['slide-in'],
|
||||
'flex gap-x-[8px] items-center',
|
||||
)}
|
||||
>
|
||||
{addonBefore}
|
||||
<div className="flex gap-x-[6px] items-center">
|
||||
<Button
|
||||
color="highlight"
|
||||
onClick={stopReceiveHomeTrigger}
|
||||
loading={loading}
|
||||
size="small"
|
||||
>
|
||||
{I18n.t('stop_receiving')}
|
||||
</Button>
|
||||
<Button
|
||||
color="brand"
|
||||
onClick={keepReceiveHomeTrigger}
|
||||
loading={loading}
|
||||
size="small"
|
||||
>
|
||||
{I18n.t('keep')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 ComponentProps, useState, type PropsWithChildren } from 'react';
|
||||
|
||||
import copy from 'copy-to-clipboard';
|
||||
import classNames from 'classnames';
|
||||
import { getReportError } from '@coze-common/chat-area-utils';
|
||||
import {
|
||||
getIsTextMessage,
|
||||
useChatArea,
|
||||
useMessageBoxContext,
|
||||
} from '@coze-common/chat-area';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozCheckMark, IconCozCopy } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Toast, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { ReportEventNames } from '../../report-events';
|
||||
import { useTooltipTrigger } from '../../hooks/use-tooltip-trigger';
|
||||
|
||||
type CopyTextMessageProps = Omit<
|
||||
ComponentProps<typeof IconButton>,
|
||||
'icon' | 'iconSize' | 'onClick'
|
||||
>;
|
||||
|
||||
export const CopyTextMessage: React.FC<
|
||||
PropsWithChildren<CopyTextMessageProps>
|
||||
> = ({ className, ...props }) => {
|
||||
const { reporter } = useChatArea();
|
||||
const { message, meta } = useMessageBoxContext();
|
||||
|
||||
const { content } = message;
|
||||
|
||||
const [isCopySuccessful, setIsCopySuccessful] = useState<boolean>(false);
|
||||
const trigger = useTooltipTrigger('hover');
|
||||
|
||||
// 单位s
|
||||
const COUNT_DOWN_TIME = 3;
|
||||
|
||||
// 单位s转化为ms的倍数
|
||||
const TIMES = 1000;
|
||||
|
||||
const handleCopy = () => {
|
||||
const resp = copy(content);
|
||||
if (resp) {
|
||||
// 复制成功
|
||||
setIsCopySuccessful(true);
|
||||
setTimeout(() => setIsCopySuccessful(false), COUNT_DOWN_TIME * TIMES);
|
||||
Toast.success({
|
||||
content: I18n.t('copy_success'),
|
||||
showClose: false,
|
||||
duration: COUNT_DOWN_TIME,
|
||||
});
|
||||
reporter.successEvent({
|
||||
eventName: ReportEventNames.CopyTextMessage,
|
||||
});
|
||||
} else {
|
||||
// 复制失败
|
||||
Toast.warning({
|
||||
content: I18n.t('copy_failed'),
|
||||
showClose: false,
|
||||
duration: COUNT_DOWN_TIME,
|
||||
});
|
||||
reporter.errorEvent({
|
||||
eventName: ReportEventNames.CopyTextMessage,
|
||||
...getReportError('copy_text_message_error', 'copy_text_message_error'),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isTextMessage = getIsTextMessage(message);
|
||||
|
||||
if (!isTextMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!meta.isGroupLastAnswerMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const iconClassNames = classNames(className, 'w-[14px] h-[14px]');
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content={isCopySuccessful ? I18n.t('copied') : I18n.t('copy')}
|
||||
trigger={trigger}
|
||||
>
|
||||
<IconButton
|
||||
data-testid="chat-area.answer-action.copy-button"
|
||||
size="small"
|
||||
icon={
|
||||
isCopySuccessful ? (
|
||||
<IconCozCheckMark className={iconClassNames} />
|
||||
) : (
|
||||
<IconCozCopy className={iconClassNames} />
|
||||
)
|
||||
}
|
||||
color="secondary"
|
||||
onClick={handleCopy}
|
||||
{...props}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 ComponentProps, type PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDeleteMessageGroup,
|
||||
useIsDeleteMessageLock,
|
||||
useMessageBoxContext,
|
||||
} from '@coze-common/chat-area';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozTrashCan } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { useTooltipTrigger } from '../../hooks/use-tooltip-trigger';
|
||||
|
||||
type DeleteMessageProps = Omit<
|
||||
ComponentProps<typeof IconButton>,
|
||||
'icon' | 'iconSize' | 'onClick'
|
||||
>;
|
||||
|
||||
export const DeleteMessage: React.FC<PropsWithChildren<DeleteMessageProps>> = ({
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const { groupId } = useMessageBoxContext();
|
||||
const trigger = useTooltipTrigger('hover');
|
||||
const isDeleteMessageLock = useIsDeleteMessageLock(groupId);
|
||||
const deleteMessageGroup = useDeleteMessageGroup();
|
||||
|
||||
return (
|
||||
<Tooltip trigger={trigger} content={I18n.t('Delete')}>
|
||||
<IconButton
|
||||
data-testid="chat-area.answer-action.delete-message-button"
|
||||
disabled={isDeleteMessageLock}
|
||||
size="small"
|
||||
icon={
|
||||
<IconCozTrashCan
|
||||
className={classNames(
|
||||
'coz-fg-hglt-red',
|
||||
className,
|
||||
'w-[14px] h-[14px]',
|
||||
)}
|
||||
/>
|
||||
}
|
||||
onClick={() => {
|
||||
// 通过 groupId 索引即可
|
||||
deleteMessageGroup(groupId);
|
||||
}}
|
||||
color="secondary"
|
||||
{...props}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -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 classNames from 'classnames';
|
||||
|
||||
export interface AnswerActionDividerProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const AnswerActionDivider: React.FC<AnswerActionDividerProps> = ({
|
||||
className,
|
||||
}) => (
|
||||
<div
|
||||
className={classNames(
|
||||
'h-[12px] border-solid border-0 border-l-[1px] coz-stroke-primary mx-[8px]',
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,63 @@
|
||||
.frown-upon-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
|
||||
border: 1px solid rgba(10, 17, 61, 6%);
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
|
||||
line-height: 20px;
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
color: rgba(6, 7, 9, 96%);
|
||||
}
|
||||
}
|
||||
|
||||
.reasons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.item {
|
||||
cursor: pointer;
|
||||
|
||||
padding: 8px 16px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
|
||||
background-color: rgba(6, 7, 9, 5%);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background-color: rgba(128, 138, 255, 20%);
|
||||
}
|
||||
|
||||
.item.selected {
|
||||
background-color: rgba(128, 138, 255, 40%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.textarea {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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 CSSProperties,
|
||||
useRef,
|
||||
useState,
|
||||
type PropsWithChildren,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useHover } from 'ahooks';
|
||||
import {
|
||||
MessageFeedbackDetailType,
|
||||
MessageFeedbackType,
|
||||
} from '@coze-common/chat-core';
|
||||
import {
|
||||
useChatAreaLayout,
|
||||
useLatestSectionId,
|
||||
useMessageBoxContext,
|
||||
} from '@coze-common/chat-area';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconCozThumbdown,
|
||||
IconCozThumbdownFill,
|
||||
IconCozCross,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { TextArea, Tooltip, Button, IconButton } from '@coze-arch/coze-design';
|
||||
import { Layout } from '@coze-common/chat-uikit-shared';
|
||||
|
||||
import { getShowFeedback } from '../../utils/get-show-feedback';
|
||||
import { useReportMessageFeedback } from '../../hooks/use-report-message-feedback';
|
||||
import { useDispatchMouseLeave } from '../../hooks/use-dispatch-mouse-leave';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface FrownUponProps {
|
||||
onClick?: () => void;
|
||||
isFrownUponPanelVisible: boolean;
|
||||
isFrownUponSuccessful: boolean;
|
||||
}
|
||||
|
||||
export interface FrownUponUIProps extends FrownUponProps {
|
||||
isMobile: boolean;
|
||||
}
|
||||
|
||||
// 点踩按钮
|
||||
export const FrownUpon: React.FC<PropsWithChildren<FrownUponProps>> = ({
|
||||
onClick,
|
||||
isFrownUponPanelVisible,
|
||||
isFrownUponSuccessful,
|
||||
}) => {
|
||||
const layout = useChatAreaLayout();
|
||||
const isMobileLayout = layout === Layout.MOBILE;
|
||||
const reportMessageFeedback = useReportMessageFeedback();
|
||||
|
||||
const { message, meta } = useMessageBoxContext();
|
||||
const latestSectionId = useLatestSectionId();
|
||||
|
||||
const handleClick = () => {
|
||||
reportMessageFeedback({
|
||||
message_feedback: {
|
||||
feedback_type: isFrownUponSuccessful
|
||||
? MessageFeedbackType.Default
|
||||
: MessageFeedbackType.Unlike,
|
||||
detail_types: isFrownUponSuccessful
|
||||
? []
|
||||
: [MessageFeedbackDetailType.UnlikeDefault],
|
||||
},
|
||||
}).then(() => {
|
||||
// 接口调用后再切换展示状态
|
||||
onClick?.();
|
||||
});
|
||||
};
|
||||
|
||||
if (!getShowFeedback({ message, meta, latestSectionId })) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FrownUponUI
|
||||
onClick={handleClick}
|
||||
isMobile={isMobileLayout}
|
||||
isFrownUponPanelVisible={isFrownUponPanelVisible}
|
||||
isFrownUponSuccessful={isFrownUponSuccessful}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const FrownUponUI: React.FC<FrownUponUIProps> = ({
|
||||
onClick,
|
||||
isFrownUponPanelVisible,
|
||||
isFrownUponSuccessful,
|
||||
isMobile,
|
||||
}) => {
|
||||
const toolTipWrapperRef = useRef<HTMLDivElement>(null);
|
||||
const isHovering = useHover(toolTipWrapperRef);
|
||||
// 解决点踩填写原因面板展开收起过程中,点踩按钮的tooltip展示错乱问题
|
||||
useDispatchMouseLeave(toolTipWrapperRef, isFrownUponPanelVisible);
|
||||
return (
|
||||
<div style={{ position: 'relative' }} ref={toolTipWrapperRef}>
|
||||
<Tooltip
|
||||
content={I18n.t('dislike')}
|
||||
getPopupContainer={() => toolTipWrapperRef.current ?? document.body}
|
||||
trigger="custom"
|
||||
visible={!isMobile && isHovering}
|
||||
>
|
||||
<IconButton
|
||||
data-testid="chat-area.answer-action.frown-upon-buton"
|
||||
size="small"
|
||||
icon={
|
||||
isFrownUponSuccessful ? (
|
||||
<IconCozThumbdownFill className="w-[14px] h-[14px]" />
|
||||
) : (
|
||||
<IconCozThumbdown className="w-[14px] h-[14px]" />
|
||||
)
|
||||
}
|
||||
color="secondary"
|
||||
onClick={onClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getFrownUponPanelReasons = () => [
|
||||
{
|
||||
label: I18n.t('dislike_feedback_tag_harm'),
|
||||
value: MessageFeedbackDetailType.UnlikeHarmful,
|
||||
},
|
||||
{
|
||||
label: I18n.t('dislike_feedback_tag_mislead'),
|
||||
value: MessageFeedbackDetailType.UnlikeIncorrect,
|
||||
},
|
||||
{
|
||||
label: I18n.t('dislike_feedback_tag_unfollow_instruction'),
|
||||
value: MessageFeedbackDetailType.UnlikeNotFollowInstructions,
|
||||
},
|
||||
{
|
||||
label: I18n.t('dislike_feedback_tag_unfollow_others'),
|
||||
value: MessageFeedbackDetailType.UnlikeOthers,
|
||||
},
|
||||
];
|
||||
|
||||
export interface FrownUponPanelProps {
|
||||
containerStyle?: CSSProperties;
|
||||
onCancel?: () => void;
|
||||
onSubmit?: () => void;
|
||||
wrapReasons?: boolean;
|
||||
}
|
||||
|
||||
export interface OnFrownUponSubmitParam {
|
||||
reasons: Array<MessageFeedbackDetailType>;
|
||||
detailContent: string;
|
||||
}
|
||||
export interface FrownUponPanelUIProps {
|
||||
onSubmit: (param: OnFrownUponSubmitParam) => void;
|
||||
onCancel: (() => void) | undefined;
|
||||
wrapReasons: boolean | undefined;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
// 点踩填写原因面板
|
||||
export const FrownUponPanel: React.FC<
|
||||
PropsWithChildren<FrownUponPanelProps>
|
||||
> = ({ containerStyle, onCancel, onSubmit, wrapReasons }) => {
|
||||
const reportMessageFeedback = useReportMessageFeedback();
|
||||
|
||||
const handleSubmit = ({ reasons, detailContent }: OnFrownUponSubmitParam) => {
|
||||
reportMessageFeedback({
|
||||
message_feedback: {
|
||||
feedback_type: MessageFeedbackType.Unlike,
|
||||
detail_types:
|
||||
reasons.length > 0
|
||||
? reasons
|
||||
: [MessageFeedbackDetailType.UnlikeDefault],
|
||||
detail_content: reasons.includes(MessageFeedbackDetailType.UnlikeOthers)
|
||||
? detailContent
|
||||
: undefined,
|
||||
},
|
||||
}).then(() => {
|
||||
// 接口调用后再切换展示状态
|
||||
onSubmit?.();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<FrownUponPanelUI
|
||||
wrapReasons={wrapReasons}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={onCancel}
|
||||
style={containerStyle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const FrownUponPanelUI: React.FC<FrownUponPanelUIProps> = ({
|
||||
onSubmit,
|
||||
onCancel,
|
||||
style,
|
||||
wrapReasons,
|
||||
}) => {
|
||||
const [reasons, setReasons] = useState<Array<MessageFeedbackDetailType>>([]);
|
||||
|
||||
const [textAreaValue, setTextAreaValue] = useState<string>('');
|
||||
|
||||
const handleItemClick = (reason: MessageFeedbackDetailType) => {
|
||||
setReasons(prev => {
|
||||
if (prev.includes(reason)) {
|
||||
return prev.filter(item => item !== reason);
|
||||
}
|
||||
return [...prev, reason];
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(s['frown-upon-container'], [
|
||||
'bg-[var(--coz-mg-card)]',
|
||||
'rounded-normal',
|
||||
])}
|
||||
style={style}
|
||||
>
|
||||
<div className={s.header}>
|
||||
<div className={s.title}>{I18n.t('dislike_feedback_title')}</div>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="secondary"
|
||||
icon={<IconCozCross />}
|
||||
onClick={onCancel}
|
||||
/>
|
||||
</div>
|
||||
<div className={classNames(s.reasons, wrapReasons && 'flex-wrap')}>
|
||||
{getFrownUponPanelReasons().map(item => (
|
||||
<span
|
||||
className={classNames(s.item, {
|
||||
[`${s.selected}`]: reasons.includes(item.value),
|
||||
})}
|
||||
onClick={() => {
|
||||
handleItemClick(item.value);
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className={s.textarea}>
|
||||
{reasons.includes(MessageFeedbackDetailType.UnlikeOthers) && (
|
||||
<TextArea
|
||||
placeholder={I18n.t('dislike_feedback_placeholder')}
|
||||
maxCount={500}
|
||||
showClear
|
||||
rows={2}
|
||||
value={textAreaValue}
|
||||
onChange={v => {
|
||||
setTextAreaValue(v);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={s.footer}>
|
||||
<Button
|
||||
theme="solid"
|
||||
onClick={() => onSubmit({ reasons, detailContent: textAreaValue })}
|
||||
>
|
||||
{I18n.t('feedback_submit')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDeleteMessageGroup,
|
||||
useIsDeleteMessageLock,
|
||||
useMessageBoxContext,
|
||||
} from '@coze-common/chat-area';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozMore, IconCozTrashCan } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Dropdown } from '@coze-arch/coze-design';
|
||||
|
||||
interface MoreOperationsProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const MoreOperations: React.FC<MoreOperationsProps> = ({
|
||||
className,
|
||||
}) => {
|
||||
const { groupId } = useMessageBoxContext();
|
||||
const isDeleteMessageLock = useIsDeleteMessageLock(groupId);
|
||||
|
||||
const deleteMessageGroup = useDeleteMessageGroup();
|
||||
return (
|
||||
<Dropdown
|
||||
render={
|
||||
<Dropdown.Menu mode="menu">
|
||||
<Dropdown.Item
|
||||
disabled={isDeleteMessageLock}
|
||||
icon={<IconCozTrashCan className="coz-fg-hglt-red" />}
|
||||
onClick={() => {
|
||||
// 通过 groupId 索引即可
|
||||
deleteMessageGroup(groupId);
|
||||
}}
|
||||
type="danger"
|
||||
>
|
||||
{I18n.t('Delete')}
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
data-testid="chat-area.answer-action.more-operation-button"
|
||||
size="small"
|
||||
color="secondary"
|
||||
icon={
|
||||
<IconCozMore className={classNames(className, 'w-[14px] h-[14px]')} />
|
||||
}
|
||||
/>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 ComponentProps, type PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
parseMarkdownToGrabNode,
|
||||
GrabElementType,
|
||||
} from '@coze-common/text-grab';
|
||||
import { messageSource, type MessageContent } from '@coze-common/chat-core';
|
||||
import {
|
||||
safeAsyncThrow,
|
||||
typeSafeJsonParseEnhanced,
|
||||
} from '@coze-common/chat-area-utils';
|
||||
import { type GrabPluginBizContext } from '@coze-common/chat-area-plugin-message-grab';
|
||||
import {
|
||||
getIsImageMessage,
|
||||
ContentType,
|
||||
getIsTextMessage,
|
||||
useMessageBoxContext,
|
||||
type WriteableChatAreaPlugin,
|
||||
} from '@coze-common/chat-area';
|
||||
import { IconCozQuotation } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { useTooltipTrigger } from '../../hooks/use-tooltip-trigger';
|
||||
import { useQuotePlugin } from '../../hooks/use-quote-plugin';
|
||||
|
||||
type QuoteMessageProps = Omit<
|
||||
ComponentProps<typeof IconButton>,
|
||||
'icon' | 'iconSize' | 'onClick'
|
||||
>;
|
||||
|
||||
export const QuoteMessage: React.FC<
|
||||
PropsWithChildren<QuoteMessageProps>
|
||||
> = props => {
|
||||
const plugin = useQuotePlugin();
|
||||
|
||||
const { message } = useMessageBoxContext();
|
||||
|
||||
if (!plugin || message.source === messageSource.Notice) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <QuoteMessageImpl {...props} />;
|
||||
};
|
||||
|
||||
/**
|
||||
* 哥哥们改动这里要小心一点喔,QuoteMessageImpl的前置依赖项是 message-grab
|
||||
*/
|
||||
export const QuoteMessageImpl: React.FC<
|
||||
PropsWithChildren<QuoteMessageProps>
|
||||
> = ({ className, ...props }) => {
|
||||
// INFO: 这里使用 as 是因为明确的知道 父组件提前尝试取 plugin 并且提前拦截的情况
|
||||
// 后续如果有改动,请务必注意这里
|
||||
const plugin = useQuotePlugin() as WriteableChatAreaPlugin<
|
||||
GrabPluginBizContext,
|
||||
unknown
|
||||
>;
|
||||
|
||||
const { chatAreaPluginContext, pluginBizContext } = plugin;
|
||||
|
||||
const { useDeleteFile } = plugin.chatAreaPluginContext.writeableHook.file;
|
||||
|
||||
const { getFileStoreInstantValues } =
|
||||
chatAreaPluginContext.readonlyAPI.batchFile;
|
||||
|
||||
const { useQuoteStore } = pluginBizContext.storeSet;
|
||||
|
||||
const { onQuote } = pluginBizContext.eventCallbacks;
|
||||
|
||||
const { updateQuoteContent, updateQuoteVisible } = useQuoteStore.getState();
|
||||
|
||||
const { message, meta } = useMessageBoxContext();
|
||||
|
||||
const { content, content_type } = message;
|
||||
|
||||
const trigger = useTooltipTrigger('hover');
|
||||
|
||||
const deleteFile = useDeleteFile();
|
||||
|
||||
const deleteAllFile = () => {
|
||||
const { fileIdList } = getFileStoreInstantValues();
|
||||
|
||||
fileIdList.forEach(id => deleteFile(id));
|
||||
};
|
||||
|
||||
const handleQuote = () => {
|
||||
deleteAllFile();
|
||||
|
||||
if (content_type === ContentType.Image) {
|
||||
const contentObj = typeSafeJsonParseEnhanced<
|
||||
MessageContent<ContentType.Image>
|
||||
>({
|
||||
str: content,
|
||||
verifyStruct: (
|
||||
_content: unknown,
|
||||
): _content is MessageContent<ContentType.Image> =>
|
||||
_content instanceof Object && 'image_list' in _content,
|
||||
onParseError: error => {
|
||||
safeAsyncThrow(error.message);
|
||||
},
|
||||
onVerifyError: error => {
|
||||
safeAsyncThrow(error.message);
|
||||
},
|
||||
});
|
||||
|
||||
updateQuoteContent(
|
||||
contentObj?.image_list.map(img => ({
|
||||
type: GrabElementType.IMAGE,
|
||||
src: img.image_ori.url,
|
||||
children: [],
|
||||
})) ?? [],
|
||||
);
|
||||
} else {
|
||||
const grabNodeList = parseMarkdownToGrabNode(content);
|
||||
updateQuoteContent(grabNodeList);
|
||||
}
|
||||
|
||||
onQuote?.({ botId: message.sender_id ?? '', source: message.source });
|
||||
|
||||
updateQuoteVisible(true);
|
||||
};
|
||||
|
||||
const isTextMessage = getIsTextMessage(message);
|
||||
const isImageMessage = getIsImageMessage(message);
|
||||
|
||||
if (!isTextMessage && !isImageMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!meta.isGroupLastAnswerMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!plugin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip content={I18n.t('quote_ask_in_chat')} trigger={trigger}>
|
||||
<IconButton
|
||||
data-testid="chat-area.answer-action.quote-message"
|
||||
size="small"
|
||||
icon={
|
||||
<IconCozQuotation
|
||||
className={classNames(className, 'w-[14px] h-[14px]')}
|
||||
/>
|
||||
}
|
||||
onClick={handleQuote}
|
||||
color="secondary"
|
||||
{...props}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
QuoteMessage.displayName = 'QuoteMessage';
|
||||
@@ -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 ComponentProps, type PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useMessageBoxContext,
|
||||
useLatestSectionId,
|
||||
} from '@coze-common/chat-area';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozRefresh } from '@coze-arch/coze-design/icons';
|
||||
import { IconButton, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { getShowRegenerate } from '../../utils/get-show-regenerate';
|
||||
import { useTooltipTrigger } from '../../hooks/use-tooltip-trigger';
|
||||
|
||||
type RegenerateMessageProps = Omit<
|
||||
ComponentProps<typeof IconButton>,
|
||||
'icon' | 'iconSize' | 'onClick'
|
||||
>;
|
||||
|
||||
export const RegenerateMessage: React.FC<
|
||||
PropsWithChildren<RegenerateMessageProps>
|
||||
> = ({ className, ...props }) => {
|
||||
const { message, meta, regenerateMessage } = useMessageBoxContext();
|
||||
const latestSectionId = useLatestSectionId();
|
||||
|
||||
const trigger = useTooltipTrigger('hover');
|
||||
|
||||
const showRegenerate = getShowRegenerate({ message, meta, latestSectionId });
|
||||
if (!showRegenerate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip trigger={trigger} content={I18n.t('message_tool_regenerate')}>
|
||||
<IconButton
|
||||
data-testid="chat-area.answer-action.regenerate-message-button"
|
||||
size="small"
|
||||
color="secondary"
|
||||
icon={
|
||||
<IconCozRefresh
|
||||
className={classNames(className, 'w-[14px] h-[14px]')}
|
||||
/>
|
||||
}
|
||||
onClick={() => {
|
||||
regenerateMessage();
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 { useHover } from 'ahooks';
|
||||
import { MessageFeedbackType } from '@coze-common/chat-core';
|
||||
import {
|
||||
useChatAreaLayout,
|
||||
useLatestSectionId,
|
||||
useMessageBoxContext,
|
||||
} from '@coze-common/chat-area';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconCozThumbsup,
|
||||
IconCozThumbsupFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip, IconButton } from '@coze-arch/coze-design';
|
||||
import { Layout } from '@coze-common/chat-uikit-shared';
|
||||
|
||||
import { getShowFeedback } from '../../utils/get-show-feedback';
|
||||
import { useReportMessageFeedback } from '../../hooks/use-report-message-feedback';
|
||||
import { useDispatchMouseLeave } from '../../hooks/use-dispatch-mouse-leave';
|
||||
|
||||
export interface ThumbsUpProps {
|
||||
isThumbsUpSuccessful?: boolean;
|
||||
onClick?: () => void;
|
||||
isFrownUponPanelVisible: boolean;
|
||||
}
|
||||
|
||||
export interface ThumbsUpUIProps extends ThumbsUpProps {
|
||||
isMobile: boolean;
|
||||
}
|
||||
|
||||
export const ThumbsUp: React.FC<ThumbsUpProps> = ({
|
||||
isThumbsUpSuccessful = false,
|
||||
onClick,
|
||||
isFrownUponPanelVisible,
|
||||
}) => {
|
||||
const layout = useChatAreaLayout();
|
||||
const isMobileLayout = layout === Layout.MOBILE;
|
||||
const reportMessageFeedback = useReportMessageFeedback();
|
||||
|
||||
const { message, meta } = useMessageBoxContext();
|
||||
const latestSectionId = useLatestSectionId();
|
||||
|
||||
const handleClick = () => {
|
||||
reportMessageFeedback({
|
||||
message_feedback: {
|
||||
feedback_type: isThumbsUpSuccessful
|
||||
? MessageFeedbackType.Default
|
||||
: MessageFeedbackType.Like,
|
||||
},
|
||||
}).then(() => {
|
||||
// 接口调用后再切换展示状态
|
||||
onClick?.();
|
||||
});
|
||||
};
|
||||
|
||||
if (!getShowFeedback({ message, meta, latestSectionId })) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ThumbsUpUI
|
||||
isMobile={isMobileLayout}
|
||||
onClick={handleClick}
|
||||
isThumbsUpSuccessful={isThumbsUpSuccessful}
|
||||
isFrownUponPanelVisible={isFrownUponPanelVisible}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ThumbsUpUI: React.FC<ThumbsUpUIProps> = ({
|
||||
onClick,
|
||||
isFrownUponPanelVisible,
|
||||
isMobile,
|
||||
isThumbsUpSuccessful,
|
||||
}) => {
|
||||
const toolTipWrapperRef = useRef<HTMLDivElement>(null);
|
||||
const isHovering = useHover(toolTipWrapperRef);
|
||||
useDispatchMouseLeave(toolTipWrapperRef, isFrownUponPanelVisible);
|
||||
|
||||
return (
|
||||
<div ref={toolTipWrapperRef}>
|
||||
<Tooltip
|
||||
trigger="custom"
|
||||
visible={!isMobile && isHovering}
|
||||
content={I18n.t('like')}
|
||||
>
|
||||
<IconButton
|
||||
data-testid="chat-area.answer-action.thumb-up-button"
|
||||
size="small"
|
||||
icon={
|
||||
isThumbsUpSuccessful ? (
|
||||
<IconCozThumbsupFill className="w-[14px] h-[14px] coz-fg-color-brand" />
|
||||
) : (
|
||||
<IconCozThumbsup className="w-[14px] h-[14px]" />
|
||||
)
|
||||
}
|
||||
color="secondary"
|
||||
onClick={onClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 {
|
||||
getIsNotificationMessage,
|
||||
useLatestSectionId,
|
||||
useMessageBoxContext,
|
||||
} from '@coze-common/chat-area';
|
||||
|
||||
export const useIsFinalAnswerMessageInLastGroup = () => {
|
||||
const { meta } = useMessageBoxContext();
|
||||
|
||||
const latestSectionId = useLatestSectionId();
|
||||
|
||||
return (
|
||||
meta.isGroupLastAnswerMessage &&
|
||||
meta.isFromLatestGroup &&
|
||||
meta.sectionId === latestSectionId
|
||||
);
|
||||
};
|
||||
|
||||
export const useIsNotificationMessage = () => {
|
||||
const { message } = useMessageBoxContext();
|
||||
return getIsNotificationMessage(message);
|
||||
};
|
||||
Reference in New Issue
Block a user