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);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type PropsWithChildren } from 'react';
|
||||
|
||||
import { AnswerActionStoreContext } from '../store/context';
|
||||
import { AnswerActionPreferenceContext } from '../preference/context';
|
||||
import { useInitStoreSet } from '../../hooks/use-init-store-set';
|
||||
|
||||
export const AnswerActionProvider: React.FC<
|
||||
PropsWithChildren<{
|
||||
enableBotTriggerControl: boolean;
|
||||
}>
|
||||
> = ({ children, enableBotTriggerControl }) => {
|
||||
const storeSet = useInitStoreSet();
|
||||
|
||||
return (
|
||||
<AnswerActionStoreContext.Provider value={storeSet}>
|
||||
<AnswerActionPreferenceContext.Provider
|
||||
value={{ enableBotTriggerControl }}
|
||||
>
|
||||
{children}
|
||||
</AnswerActionPreferenceContext.Provider>
|
||||
</AnswerActionStoreContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { type ChatAnswerActionPreference } from './type';
|
||||
|
||||
export const AnswerActionPreferenceContext =
|
||||
createContext<ChatAnswerActionPreference>({
|
||||
enableBotTriggerControl: false,
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { useContext } from 'react';
|
||||
|
||||
import { AnswerActionPreferenceContext } from './context';
|
||||
|
||||
export const useAnswerActionPreference = () =>
|
||||
useContext(AnswerActionPreferenceContext);
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 interface ChatAnswerActionPreference {
|
||||
enableBotTriggerControl: boolean;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 { createContext, type PropsWithChildren } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
|
||||
import {
|
||||
type ReportMessageFeedbackFn,
|
||||
type ReportMessageFeedbackFnProviderProps,
|
||||
} from './type';
|
||||
|
||||
export const ReportMessageFeedbackFnContext =
|
||||
createContext<ReportMessageFeedbackFn | null>(null);
|
||||
|
||||
export const ReportMessageFeedbackFnProvider: React.FC<
|
||||
PropsWithChildren<ReportMessageFeedbackFnProviderProps>
|
||||
> = ({ children, reportMessageFeedback }) => {
|
||||
const { runAsync: asyncReportMessage } = useRequest(reportMessageFeedback, {
|
||||
manual: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<ReportMessageFeedbackFnContext.Provider value={asyncReportMessage}>
|
||||
{children}
|
||||
</ReportMessageFeedbackFnContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { ReportMessageFeedbackFnContext } from './context';
|
||||
|
||||
export { ReportMessageFeedbackFnProvider } from './context';
|
||||
|
||||
export const useReportMessageFeedbackFn = () => {
|
||||
const reportMessageFeedbackFn = useContext(ReportMessageFeedbackFnContext);
|
||||
if (!reportMessageFeedbackFn) {
|
||||
throw new Error('reportMessageFeedbackFn not provided');
|
||||
}
|
||||
return reportMessageFeedbackFn;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 ChatCore } from '@coze-common/chat-core';
|
||||
export type ReportMessageFeedbackFn = ChatCore['reportMessage'];
|
||||
|
||||
export interface ReportMessageFeedbackFnProviderProps {
|
||||
reportMessageFeedback: ReportMessageFeedbackFn;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 { createContext } from 'react';
|
||||
|
||||
import { type StoreSet } from './type';
|
||||
|
||||
export const AnswerActionStoreContext = createContext<StoreSet | null>(null);
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AnswerActionStoreContext } from './context';
|
||||
|
||||
export const useAnswerActionStore = () => {
|
||||
const storeSet = useContext(AnswerActionStoreContext);
|
||||
if (!storeSet) {
|
||||
throw new Error('answer action store not provided');
|
||||
}
|
||||
return storeSet;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 FavoriteBotTriggerConfigStore } from '../../store/favorite-bot-trigger-config';
|
||||
|
||||
export interface StoreSet {
|
||||
useFavoriteBotTriggerConfigStore: FavoriteBotTriggerConfigStore;
|
||||
}
|
||||
@@ -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 RefObject, useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* 点击赞、踩按钮,可以关闭打开原因填写面板
|
||||
* 填写面板关闭的时候, 会造成一次 Reflow。此时赞、踩按钮的位置会发生变化, 鼠标已经不在按钮上,但是对应按钮元素不会处罚 mouseleave 事件
|
||||
* 由于不触发 mouseleave 造成按钮上的 tooltip 不消失、错位等问题
|
||||
* 所以需要在面板 visible 变化时 patch 一个 mouseleave 事件
|
||||
*/
|
||||
export const useDispatchMouseLeave = (
|
||||
ref: RefObject<HTMLDivElement>,
|
||||
isFrownUponPanelVisible: boolean,
|
||||
) => {
|
||||
useEffect(() => {
|
||||
ref.current?.dispatchEvent(
|
||||
new MouseEvent('mouseleave', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
);
|
||||
}, [isFrownUponPanelVisible, ref.current]);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 { useRequest } from 'ahooks';
|
||||
import { type GetBotParticipantInfoByBotIdsResponse } from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { type BotParticipantInfoWithId } from '../store/favorite-bot-trigger-config';
|
||||
import { useAnswerActionStore } from '../context/store';
|
||||
|
||||
export const useGetBotParticipantInfo = ({
|
||||
botId,
|
||||
isEnabled,
|
||||
}: {
|
||||
botId: string | undefined;
|
||||
isEnabled: boolean;
|
||||
}) => {
|
||||
const { useFavoriteBotTriggerConfigStore } = useAnswerActionStore();
|
||||
|
||||
useRequest(
|
||||
(): Promise<GetBotParticipantInfoByBotIdsResponse | undefined> => {
|
||||
if (!botId) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return DeveloperApi.GetBotParticipantInfoByBotIds({
|
||||
bot_ids: [botId],
|
||||
});
|
||||
},
|
||||
{
|
||||
ready: isEnabled && Boolean(botId),
|
||||
refreshDeps: [isEnabled, botId],
|
||||
onSuccess: res => {
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { updateMapByConfigList } =
|
||||
useFavoriteBotTriggerConfigStore.getState();
|
||||
|
||||
const participantInfoList: BotParticipantInfoWithId[] = Object.entries(
|
||||
res.participant_info_map ?? {},
|
||||
).map(([participantBotId, item]) => ({
|
||||
...item,
|
||||
botId: participantBotId,
|
||||
}));
|
||||
|
||||
updateMapByConfigList(participantInfoList);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -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 { useCreation } from 'ahooks';
|
||||
|
||||
import { createFavoriteBotTriggerConfigStore } from '../store/favorite-bot-trigger-config';
|
||||
import { type StoreSet } from '../context/store/type';
|
||||
|
||||
export const useInitStoreSet = (): StoreSet => {
|
||||
const useFavoriteBotTriggerConfigStore = useCreation(
|
||||
() => createFavoriteBotTriggerConfigStore(),
|
||||
[],
|
||||
);
|
||||
return { useFavoriteBotTriggerConfigStore };
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 GrabPluginBizContext } from '@coze-common/chat-area-plugin-message-grab';
|
||||
import { PluginName, useWriteablePlugin } from '@coze-common/chat-area';
|
||||
|
||||
export const useQuotePlugin = () => {
|
||||
try {
|
||||
const plugin = useWriteablePlugin<GrabPluginBizContext>(
|
||||
PluginName.MessageGrab,
|
||||
);
|
||||
|
||||
return plugin;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 { useToggle } from 'ahooks';
|
||||
import {
|
||||
ReportMessageAction,
|
||||
type ReportMessageProps,
|
||||
} from '@coze-common/chat-core';
|
||||
import { getReportError } from '@coze-common/chat-area-utils';
|
||||
import {
|
||||
isFallbackErrorMessage,
|
||||
useChatArea,
|
||||
useMessageBoxContext,
|
||||
} from '@coze-common/chat-area';
|
||||
|
||||
import { ReportEventNames } from '../report-events';
|
||||
import { useReportMessageFeedbackFn } from '../context/report-message-feedback';
|
||||
|
||||
/**
|
||||
* @description 消息点赞/点踩
|
||||
*/
|
||||
export const useReportMessageFeedback = () => {
|
||||
const { reporter } = useChatArea();
|
||||
const asyncReportMessage = useReportMessageFeedbackFn();
|
||||
const { message } = useMessageBoxContext();
|
||||
const { message_id } = message;
|
||||
|
||||
const reportMessageFeedback = async (
|
||||
params: Pick<ReportMessageProps, 'message_feedback'>,
|
||||
) => {
|
||||
if (isFallbackErrorMessage(message)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await asyncReportMessage({
|
||||
message_id,
|
||||
action: ReportMessageAction.Feedback,
|
||||
...params,
|
||||
});
|
||||
|
||||
reporter.successEvent({ eventName: ReportEventNames.ReportMessage });
|
||||
} catch (e) {
|
||||
reporter.errorEvent({
|
||||
eventName: ReportEventNames.ReportMessage,
|
||||
...getReportError(e),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return reportMessageFeedback;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 获取 点赞按钮组件/点踩按钮组件/点踩原因填写面板组件 props
|
||||
*/
|
||||
|
||||
export const useReportMessageFeedbackHelpers = () => {
|
||||
// 点赞成功标识
|
||||
const [isThumbsUpSuccessful, { toggle: toogleIsThumbsUpSuccessful }] =
|
||||
useToggle<boolean>(false);
|
||||
|
||||
// 点踩成功标识
|
||||
const [isFrownUponSuccessful, { toggle: toogleIsFrownUponSuccessful }] =
|
||||
useToggle<boolean>(false);
|
||||
|
||||
// 点踩原因填写面板展示
|
||||
const [
|
||||
isFrownUponPanelVisible,
|
||||
{
|
||||
setLeft: setIsFrownUponPanelVisibleFalse,
|
||||
setRight: setIsFrownUponPanelVisibleTrue,
|
||||
},
|
||||
] = useToggle<boolean>(false);
|
||||
|
||||
// 点赞按钮组件onClick事件
|
||||
const thumbsUpOnClick = () => {
|
||||
toogleIsThumbsUpSuccessful();
|
||||
// 点赞/点踩互斥
|
||||
if (!isThumbsUpSuccessful && isFrownUponSuccessful) {
|
||||
toogleIsFrownUponSuccessful();
|
||||
setIsFrownUponPanelVisibleFalse();
|
||||
}
|
||||
};
|
||||
|
||||
// 点踩按钮组件onClick事件
|
||||
const frownUponOnClick = () => {
|
||||
toogleIsFrownUponSuccessful();
|
||||
// 点赞/点踩互斥
|
||||
if (!isFrownUponSuccessful && isThumbsUpSuccessful) {
|
||||
toogleIsThumbsUpSuccessful();
|
||||
}
|
||||
|
||||
if (!isFrownUponSuccessful) {
|
||||
setIsFrownUponPanelVisibleTrue();
|
||||
} else {
|
||||
setIsFrownUponPanelVisibleFalse();
|
||||
}
|
||||
};
|
||||
|
||||
// 点踩原因填写面板组件onCancel事件
|
||||
const frownUponPanelonCancel = () => {
|
||||
setIsFrownUponPanelVisibleFalse();
|
||||
};
|
||||
|
||||
// 点踩原因填写面板组件onSubmit事件
|
||||
const frownUponPanelonSubmit = () => {
|
||||
setIsFrownUponPanelVisibleFalse();
|
||||
// 点赞/点踩互斥
|
||||
if (isThumbsUpSuccessful) {
|
||||
toogleIsThumbsUpSuccessful();
|
||||
}
|
||||
if (!isFrownUponSuccessful) {
|
||||
toogleIsFrownUponSuccessful();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
isThumbsUpSuccessful,
|
||||
isFrownUponSuccessful,
|
||||
isFrownUponPanelVisible,
|
||||
thumbsUpOnClick,
|
||||
frownUponOnClick,
|
||||
frownUponPanelonCancel,
|
||||
frownUponPanelonSubmit,
|
||||
};
|
||||
};
|
||||
@@ -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 { useChatAreaLayout } from '@coze-common/chat-area/hooks/public/common';
|
||||
import { Layout } from '@coze-common/chat-uikit-shared';
|
||||
|
||||
export const useTooltipTrigger = (
|
||||
defaultTrigger: 'hover' | 'click' | 'focus' | 'contextMenu',
|
||||
) => {
|
||||
const layout = useChatAreaLayout();
|
||||
if (layout === Layout.MOBILE) {
|
||||
return 'custom';
|
||||
}
|
||||
return defaultTrigger;
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 { useRequest } from 'ahooks';
|
||||
import { TriggerEnabled } from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
|
||||
export const useUpdateHomeTriggerConfig = ({
|
||||
botId,
|
||||
onSuccess,
|
||||
}: {
|
||||
botId: string | undefined;
|
||||
onSuccess?: (isKeepReceiveTrigger: boolean) => void;
|
||||
}) => {
|
||||
const { run, loading } = useRequest(
|
||||
async ({ isKeepReceiveTrigger }: { isKeepReceiveTrigger: boolean }) => {
|
||||
if (!botId) {
|
||||
throw new Error('try to request home trigger but no bot id');
|
||||
}
|
||||
await DeveloperApi.UpdateHomeTriggerUserConfig({
|
||||
bot_id: botId,
|
||||
action: isKeepReceiveTrigger
|
||||
? TriggerEnabled.Open
|
||||
: TriggerEnabled.Close,
|
||||
});
|
||||
return isKeepReceiveTrigger;
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess,
|
||||
},
|
||||
);
|
||||
return {
|
||||
keepReceiveHomeTrigger: () => run({ isKeepReceiveTrigger: true }),
|
||||
stopReceiveHomeTrigger: () => run({ isKeepReceiveTrigger: false }),
|
||||
loading,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 { ActionBarContainer } from './components/action-bar-container';
|
||||
export { ActionBarHoverContainer } from './components/action-bar-hover-container';
|
||||
export {
|
||||
ThumbsUp,
|
||||
ThumbsUpUI,
|
||||
ThumbsUpProps,
|
||||
ThumbsUpUIProps,
|
||||
} from './components/thumbs-up';
|
||||
export { RegenerateMessage } from './components/regenerate-message';
|
||||
export { MoreOperations } from './components/more-operations';
|
||||
export {
|
||||
FrownUpon,
|
||||
FrownUponUI,
|
||||
FrownUponProps,
|
||||
FrownUponUIProps,
|
||||
FrownUponPanel,
|
||||
FrownUponPanelUI,
|
||||
FrownUponPanelProps,
|
||||
FrownUponPanelUIProps,
|
||||
OnFrownUponSubmitParam,
|
||||
} from './components/frown-upon';
|
||||
export { DeleteMessage } from './components/delete-message';
|
||||
export { CopyTextMessage } from './components/copy-text-message';
|
||||
export { QuoteMessage } from './components/quote-message';
|
||||
|
||||
export {
|
||||
useReportMessageFeedback,
|
||||
useReportMessageFeedbackHelpers,
|
||||
} from './hooks/use-report-message-feedback';
|
||||
export { useTooltipTrigger } from './hooks/use-tooltip-trigger';
|
||||
export { AnswerActionProvider } from './context/main';
|
||||
export {
|
||||
AnswerActionDivider,
|
||||
type AnswerActionDividerProps,
|
||||
} from './components/divider';
|
||||
export {
|
||||
BotTriggerConfigButtonGroup,
|
||||
type BotTriggerConfigButtonGroupProps,
|
||||
} from './components/bot-trigger-config-button-group';
|
||||
export { useAnswerActionStore } from './context/store';
|
||||
export {
|
||||
ReportMessageFeedbackFnProvider,
|
||||
useReportMessageFeedbackFn,
|
||||
} from './context/report-message-feedback';
|
||||
export { BotParticipantInfoWithId } from './store/favorite-bot-trigger-config';
|
||||
export { useUpdateHomeTriggerConfig } from './hooks/use-update-home-trigger-config';
|
||||
export { useDispatchMouseLeave } from './hooks/use-dispatch-mouse-leave';
|
||||
|
||||
export { ReportEventNames } from './report-events';
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export enum ReportEventNames {
|
||||
/** 原名: chat_area_tts_voice_ws */
|
||||
TtsVoiceWs = 'chat_answer_action_start_TTS',
|
||||
/** 原名: chat_area_report_message */
|
||||
ReportMessage = 'chat_answer_action_message_feedback',
|
||||
/** 原名: chat_area_copy_text_message */
|
||||
CopyTextMessage = 'chat_answer_action_copy_text_message',
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 { devtools } from 'zustand/middleware';
|
||||
import { create } from 'zustand';
|
||||
import { produce } from 'immer';
|
||||
import { type BotParticipantInfo } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
type BotId = string;
|
||||
|
||||
export type BotParticipantInfoWithId = BotParticipantInfo & { botId: string };
|
||||
|
||||
export interface FavoriteBotTriggerConfigState {
|
||||
favoriteBotTriggerConfigMap: Record<BotId, BotParticipantInfo>;
|
||||
}
|
||||
export interface FavoriteBotTriggerConfigAction {
|
||||
updateFavoriteBotTriggerConfigMap: (
|
||||
map: Record<BotId, BotParticipantInfo>,
|
||||
) => void;
|
||||
updateFavoriteBotTriggerConfigMapByImmer: (
|
||||
updateFn: (map: Record<BotId, BotParticipantInfo>) => void,
|
||||
) => void;
|
||||
updateMapByConfigList: (list: BotParticipantInfoWithId[]) => void;
|
||||
getFavoriteBotConfigIdList: () => BotId[];
|
||||
deleteConfigById: (id: BotId) => void;
|
||||
}
|
||||
|
||||
export const createFavoriteBotTriggerConfigStore = () =>
|
||||
create<FavoriteBotTriggerConfigState & FavoriteBotTriggerConfigAction>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
favoriteBotTriggerConfigMap: {},
|
||||
updateFavoriteBotTriggerConfigMap: map => {
|
||||
set(
|
||||
{
|
||||
favoriteBotTriggerConfigMap: Object.assign(
|
||||
{},
|
||||
get().favoriteBotTriggerConfigMap,
|
||||
map,
|
||||
),
|
||||
},
|
||||
false,
|
||||
'updateFavoriteBotTriggerConfigMap',
|
||||
);
|
||||
},
|
||||
updateMapByConfigList: list => {
|
||||
const map = Object.fromEntries(list.map(item => [item.botId, item]));
|
||||
set(
|
||||
{
|
||||
favoriteBotTriggerConfigMap: Object.assign(
|
||||
{},
|
||||
get().favoriteBotTriggerConfigMap,
|
||||
map,
|
||||
),
|
||||
},
|
||||
false,
|
||||
'updateMapByConfigList',
|
||||
);
|
||||
},
|
||||
updateFavoriteBotTriggerConfigMapByImmer: updateFn => {
|
||||
set(
|
||||
{
|
||||
favoriteBotTriggerConfigMap: produce<
|
||||
FavoriteBotTriggerConfigState['favoriteBotTriggerConfigMap']
|
||||
>(get().favoriteBotTriggerConfigMap, updateFn),
|
||||
},
|
||||
false,
|
||||
'updateFavoriteBotTriggerConfigMapByImmer',
|
||||
);
|
||||
},
|
||||
deleteConfigById: id => {
|
||||
set(
|
||||
{
|
||||
favoriteBotTriggerConfigMap: produce(
|
||||
get().favoriteBotTriggerConfigMap,
|
||||
map => {
|
||||
delete map[id];
|
||||
},
|
||||
),
|
||||
},
|
||||
false,
|
||||
'deleteConfigById',
|
||||
);
|
||||
},
|
||||
getFavoriteBotConfigIdList: () =>
|
||||
Object.entries(get().favoriteBotTriggerConfigMap).map(([id]) => id),
|
||||
}),
|
||||
{
|
||||
enabled: IS_DEV_MODE,
|
||||
name: 'botStudio.ChatAnswerActionBotTrigger',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export type FavoriteBotTriggerConfigStore = ReturnType<
|
||||
typeof createFavoriteBotTriggerConfigStore
|
||||
>;
|
||||
17
frontend/packages/common/chat-area/chat-answer-action/src/typings.d.ts
vendored
Normal file
17
frontend/packages/common/chat-area/chat-answer-action/src/typings.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
@@ -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 { type MessageMeta } from '@coze-common/chat-area';
|
||||
|
||||
export const getIsLastGroup = ({
|
||||
meta: { isFromLatestGroup, sectionId },
|
||||
latestSectionId,
|
||||
}: {
|
||||
meta: Pick<MessageMeta, 'isFromLatestGroup' | 'sectionId'>;
|
||||
latestSectionId: string;
|
||||
}) => isFromLatestGroup && sectionId === latestSectionId;
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import {
|
||||
type Message,
|
||||
getIsNotificationMessage,
|
||||
getIsTriggerMessage,
|
||||
getIsAsyncResultMessage,
|
||||
} from '@coze-common/chat-area';
|
||||
|
||||
export const getIsPushedMessage = (
|
||||
message: Pick<Message, 'type' | 'source'>,
|
||||
): boolean =>
|
||||
getIsTriggerMessage(message) ||
|
||||
getIsNotificationMessage(message) ||
|
||||
getIsAsyncResultMessage(message);
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 { messageSource, taskType } from '@coze-common/chat-core';
|
||||
import { type Message, type MessageMeta } from '@coze-common/chat-area';
|
||||
|
||||
import { getIsLastGroup } from './get-is-last-group';
|
||||
|
||||
export const getShowBotTriggerButton = ({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- .
|
||||
message: { source, extra_info },
|
||||
meta,
|
||||
latestSectionId,
|
||||
}: {
|
||||
message: Pick<Message, 'source' | 'extra_info'>;
|
||||
meta: Pick<MessageMeta, 'isFromLatestGroup' | 'sectionId'>;
|
||||
latestSectionId: string;
|
||||
}) =>
|
||||
source === messageSource.TaskManualTrigger &&
|
||||
extra_info.task_type === taskType.PresetTask &&
|
||||
getIsLastGroup({ meta, latestSectionId });
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type Message, type MessageMeta } from '@coze-common/chat-area';
|
||||
|
||||
import { getIsPushedMessage } from './get-is-pushed-message';
|
||||
|
||||
export const getShowFeedback = ({
|
||||
message,
|
||||
meta,
|
||||
latestSectionId,
|
||||
}: {
|
||||
message: Pick<Message, 'type' | 'source'>;
|
||||
meta: Pick<
|
||||
MessageMeta,
|
||||
'isFromLatestGroup' | 'sectionId' | 'isGroupLastAnswerMessage'
|
||||
>;
|
||||
latestSectionId: string;
|
||||
}): boolean => {
|
||||
// 是否是推送的消息
|
||||
const isPushedMessage = getIsPushedMessage(message);
|
||||
if (isPushedMessage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 来自最后一个消息组的 final answer
|
||||
return (
|
||||
meta.isGroupLastAnswerMessage &&
|
||||
meta.isFromLatestGroup &&
|
||||
meta.sectionId === latestSectionId
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type Message, type MessageMeta } from '@coze-common/chat-area';
|
||||
|
||||
import { getIsPushedMessage } from './get-is-pushed-message';
|
||||
import { getIsLastGroup } from './get-is-last-group';
|
||||
|
||||
export const getShowRegenerate = ({
|
||||
message,
|
||||
meta,
|
||||
latestSectionId,
|
||||
}: {
|
||||
message: Pick<Message, 'type' | 'source'>;
|
||||
meta: Pick<MessageMeta, 'isFromLatestGroup' | 'sectionId'>;
|
||||
latestSectionId: string;
|
||||
}): boolean => {
|
||||
// 是否是推送的消息
|
||||
const isPushedMessage = getIsPushedMessage(message);
|
||||
if (isPushedMessage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 来自最后一个消息组
|
||||
return getIsLastGroup({ meta, latestSectionId });
|
||||
};
|
||||
Reference in New Issue
Block a user