chore: replace all cn comments of fe to en version by volc api (#320)
This commit is contained in:
@@ -14,6 +14,6 @@
|
||||
background-color: #AAA;
|
||||
border-radius: 8px;
|
||||
|
||||
transition: height 0.1s ease-in-out; /* 设置过渡效果 */
|
||||
transition: height 0.1s ease-in-out; /* Set transition effects */
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ const MoreButton: FC<IProps> = props => {
|
||||
|
||||
return (
|
||||
<UIKitTooltip
|
||||
// 为了点调起选择文件的事件时收起 tooltip
|
||||
// Collapse tooltip when the selected file event is invoked
|
||||
disableFocusListener
|
||||
content={tooltipContent}
|
||||
hideToolTip={layout === Layout.MOBILE}
|
||||
|
||||
@@ -181,15 +181,15 @@ export const ChatInput = forwardRef<InputRefObject, IChatInputProps>(
|
||||
onClearContext?.();
|
||||
};
|
||||
/**
|
||||
* 处理清除历史记录按钮点击事件
|
||||
* Handle the Clear History button click event
|
||||
*/
|
||||
const handleClearHistoryButtonClick = () => {
|
||||
onClearHistory?.();
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理用户发送消息
|
||||
* @param text 用户发送消息的文本
|
||||
* Handle user sending messages
|
||||
* @Param text The text of the message sent by the user
|
||||
*/
|
||||
const handleSendMessage = (text: string) => {
|
||||
onSendMessage?.({
|
||||
@@ -223,7 +223,7 @@ export const ChatInput = forwardRef<InputRefObject, IChatInputProps>(
|
||||
if (
|
||||
!isMultiLines &&
|
||||
height > DEFAULT_HEIGHT &&
|
||||
// 通过 inputText 长度判断,排除 placeholder 导致的 resize 不处理
|
||||
// Judging by the length of inputText, excluding the resize caused by placeholder will not be processed.
|
||||
inputText?.trim()?.length !== 0
|
||||
) {
|
||||
setIsMultiLines(true);
|
||||
@@ -238,7 +238,7 @@ export const ChatInput = forwardRef<InputRefObject, IChatInputProps>(
|
||||
}
|
||||
};
|
||||
|
||||
/** 计算出复合条件的readonly值 */
|
||||
/** Calculate the readonly value of the compound condition */
|
||||
const finalClearHistoryButtonDisable =
|
||||
isClearHistoryButtonDisabled || isReadonly;
|
||||
const finalSendButtonDisable =
|
||||
|
||||
@@ -45,7 +45,7 @@ export const useTextSend = ({
|
||||
onSubmit: (text: string) => void;
|
||||
defaultValue?: string;
|
||||
/**
|
||||
* 是否允许空字符串提交
|
||||
* Whether to allow empty string submission
|
||||
* @default false
|
||||
*/
|
||||
allowEmpty?: boolean;
|
||||
@@ -172,10 +172,10 @@ export const useTextSend = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算光标的当前位置
|
||||
// Calculate the current position of the cursor
|
||||
const cursorPosition = textarea.selectionStart;
|
||||
|
||||
// 在光标位置插入新行
|
||||
// Inserts a new row at the cursor position
|
||||
const newValue = `${inputText.substring(
|
||||
0,
|
||||
cursorPosition,
|
||||
|
||||
@@ -29,18 +29,18 @@ import {
|
||||
|
||||
interface IChatUploadProps {
|
||||
/**
|
||||
* 上传事件回调
|
||||
* @param uploadType 上传类型 [IMAGE=0 FILE=1]
|
||||
* @param file 文件
|
||||
* Upload event callback
|
||||
* @param uploadType [IMAGE = 0 FILE = 1]
|
||||
* @param file
|
||||
* @returns void
|
||||
*/
|
||||
onUpload: (uploadType: UploadType, file: File) => void;
|
||||
/**
|
||||
* 文案信息配置
|
||||
* Copywriting information configuration
|
||||
*/
|
||||
copywritingConfig?: IChatUploadCopywritingConfig;
|
||||
/**
|
||||
* 文件最大尺寸(单位byte)
|
||||
* Maximum file size (in bytes)
|
||||
*/
|
||||
maxFileSize?: number;
|
||||
isDisabled?: boolean;
|
||||
@@ -66,8 +66,8 @@ export const ChatUpload: FC<IChatUploadProps> = props => {
|
||||
} = props;
|
||||
|
||||
/**
|
||||
* 处理上传
|
||||
* @param fileList 文件List
|
||||
* handle uploads
|
||||
* @param fileList
|
||||
* @returns void
|
||||
*/
|
||||
const handleUpload = (fileList: File[]) => {
|
||||
@@ -86,13 +86,13 @@ export const ChatUpload: FC<IChatUploadProps> = props => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 是否存在超出大小的文件
|
||||
// Is there a file that is out of size?
|
||||
const hasOverflowLimitFileSize = fileList.some(
|
||||
file => file.size > maxFileSize,
|
||||
);
|
||||
const hasEmptyFile = fileList.some(file => file.size <= 0);
|
||||
|
||||
// 文件大小超过预期大小的错误处理
|
||||
// Error handling if the file size exceeds the expected size
|
||||
if (hasOverflowLimitFileSize) {
|
||||
Toast.warning({
|
||||
showClose: false,
|
||||
|
||||
@@ -57,13 +57,13 @@ interface OnBoardingProps {
|
||||
prologue?: string;
|
||||
suggestionList?: IMessage[];
|
||||
/**
|
||||
* suggestionList是否换行展示, 默认false
|
||||
* SuggestionList Whether to wrap display, default false
|
||||
*/
|
||||
suggestionsWrap?: boolean;
|
||||
readonly?: boolean;
|
||||
suggestionListWithString?: string[];
|
||||
/**
|
||||
* suggestionListWithString是否换行展示, 默认false
|
||||
* suggestionListWithString whether to wrap, default false
|
||||
*/
|
||||
suggestionsWithStringWrap?: boolean;
|
||||
onSuggestionClick?: (content: string) => void;
|
||||
|
||||
@@ -45,7 +45,7 @@ export const Gradient: React.FC<{
|
||||
'transition-all duration-500': !preview,
|
||||
})}
|
||||
style={{
|
||||
[direction]: `${(position > 0 ? position : 0) * 100 - 0.1}%`, // 0.1为阴影补偿,防止出现间隙
|
||||
[direction]: `${(position > 0 ? position : 0) * 100 - 0.1}%`, // 0.1 is shadow compensation to prevent gaps.
|
||||
width: '10%',
|
||||
background,
|
||||
opacity: showGradient ? 1 : 0,
|
||||
@@ -58,7 +58,7 @@ const getGradient = (
|
||||
cropperWidth = 1,
|
||||
) => {
|
||||
const { left: cropperImgLeft = 0, width: cropperImgWidth = 0 } = canvasData;
|
||||
// 伪裁剪 兼容历史gradient不准的问题
|
||||
// Pseudo-cropping, compatibility with inaccurate historical gradients
|
||||
if (!isEmpty(canvasData)) {
|
||||
return {
|
||||
left: cropperImgLeft / cropperWidth,
|
||||
@@ -97,7 +97,7 @@ export const WithRuleImgBackground: React.FC<WithRuleImgBackgroundProps> = ({
|
||||
|
||||
const [themeColor, setThemeColor] = useState(theme_color ?? 'transparent');
|
||||
|
||||
// 与裁剪框等比例算出图片渲染区域的宽度
|
||||
// Calculate the width of the image rendering area in proportion to the cropping box
|
||||
const imgWidth = (targetHeight * cropperSize.width) / cropperSize.height;
|
||||
|
||||
const mediumColor = addAlpha(themeColor, 0.95);
|
||||
@@ -120,7 +120,7 @@ export const WithRuleImgBackground: React.FC<WithRuleImgBackgroundProps> = ({
|
||||
zIndex: preview ? 100 : 0,
|
||||
}}
|
||||
>
|
||||
{/* 背景图上压黑阴影 */}
|
||||
{/* Black shadow on background cover */}
|
||||
<div className="bg-[rgba(0,0,0,0.12)] absolute w-full h-full z-[200] rounded-t-[6px]"></div>
|
||||
|
||||
<div className="relative w-fit h-fit left-1/2 -translate-x-1/2">
|
||||
|
||||
@@ -27,22 +27,22 @@ export interface CanvasPosition {
|
||||
}
|
||||
|
||||
export interface BackgroundImageDetail {
|
||||
/** 原始图片 */
|
||||
/** original image */
|
||||
origin_image_uri?: string;
|
||||
origin_image_url?: string;
|
||||
/** 实际使用图片 */
|
||||
/** Actual use of pictures */
|
||||
image_uri?: string;
|
||||
image_url?: string;
|
||||
theme_color?: string;
|
||||
/** 渐变位置 */
|
||||
/** Gradual change of position */
|
||||
gradient_position?: GradientPosition;
|
||||
/** 裁剪画布位置 */
|
||||
/** Crop canvas position */
|
||||
canvas_position?: CanvasPosition;
|
||||
}
|
||||
|
||||
export interface BackgroundImageInfo {
|
||||
/** web端背景图 */
|
||||
/** Web background cover */
|
||||
web_background_image?: BackgroundImageDetail;
|
||||
/** 移动端背景图 */
|
||||
/** Mobile end background cover */
|
||||
mobile_background_image?: BackgroundImageDetail;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { MODE_CONFIG } from './const';
|
||||
|
||||
// 输入透明度系数 和color 返回新的颜色
|
||||
// Enter the transparency factor and color to return a new color
|
||||
export function addAlpha(color: string, alpha: number): string {
|
||||
const regex = /^rgba\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$/;
|
||||
if (!regex.test(color)) {
|
||||
@@ -31,11 +31,11 @@ export function addAlpha(color: string, alpha: number): string {
|
||||
return newColor;
|
||||
}
|
||||
|
||||
// 图片的宽高比
|
||||
// Image aspect ratio
|
||||
export const getStandardRatio = (mode: 'pc' | 'mobile'): number =>
|
||||
MODE_CONFIG[mode].size.width / MODE_CONFIG[mode].size.height;
|
||||
|
||||
// 计算是否展示渐变阴影 = 屏幕宽度 > 图片宽度 * (1- 2 * 左/右阴影位置)
|
||||
// Calculate whether to display gradual change Shadow = Screen Width > Image Width * (1- 2 * Left/Right Shadow Position)
|
||||
export const computeShowGradient = (
|
||||
width: number,
|
||||
imgWidth: number,
|
||||
|
||||
@@ -60,39 +60,39 @@ export interface EnhancedContentConfig {
|
||||
|
||||
export interface IContentBoxProps {
|
||||
/**
|
||||
* Core SDK的消息体内容
|
||||
* Message body content of the Core SDK
|
||||
*/
|
||||
message: IMessage;
|
||||
/**
|
||||
* 事件回调对象
|
||||
* event callback object
|
||||
*/
|
||||
eventCallbacks?: IEventCallbacks;
|
||||
/**
|
||||
* content卡片配置的内容
|
||||
* Content of the card configuration
|
||||
*/
|
||||
contentConfigs?: IContentConfigs;
|
||||
/**
|
||||
* 是否只读
|
||||
* Is it read-only?
|
||||
*/
|
||||
readonly?: boolean;
|
||||
getBotInfo: GetBotInfo;
|
||||
layout: Layout;
|
||||
/**
|
||||
* 在 mix 模式下,给 text 格式卡片加插槽
|
||||
* In mix mode, slot the text card
|
||||
*/
|
||||
multimodalTextContentAddonTop?: ReactNode;
|
||||
showBackground: boolean;
|
||||
/**
|
||||
* 启用自动适应图片能力
|
||||
* Enable the ability to automatically adapt pictures
|
||||
*/
|
||||
enableAutoSizeImage?: boolean;
|
||||
|
||||
/**
|
||||
* mdBox的配置
|
||||
* mdBox configuration
|
||||
*/
|
||||
mdBoxProps?: MdBoxProps;
|
||||
/**
|
||||
* 卡片状态是否为disabled
|
||||
* Is the card status disabled?
|
||||
*/
|
||||
isCardDisabled?: boolean;
|
||||
isContentLoading?: boolean;
|
||||
@@ -114,7 +114,7 @@ export const ContentBox: FC<IContentBoxProps> = props => {
|
||||
enhancedContentConfigList,
|
||||
} = props;
|
||||
/**
|
||||
* Content内容启用配置 Start
|
||||
* Content Enable Configuration Start
|
||||
*/
|
||||
const isTextEnable = defaultEnable(
|
||||
contentConfigs?.[ContentBoxType.TEXT]?.enable,
|
||||
@@ -127,7 +127,7 @@ export const ContentBox: FC<IContentBoxProps> = props => {
|
||||
const isSimpleFunctionEnable =
|
||||
contentConfigs?.[ContentBoxType.SIMPLE_FUNCTION]?.enable;
|
||||
/**
|
||||
* Content内容启用配置 End
|
||||
* Content Enable Configuration End
|
||||
*/
|
||||
|
||||
const enhancedContentConfig = enhancedContentConfigList?.find(config =>
|
||||
@@ -143,8 +143,8 @@ export const ContentBox: FC<IContentBoxProps> = props => {
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 文本类型的处理
|
||||
* 这里目前有两种情况,第一种message.type = 'follow_up' 代表是suggestion 第二种反之是普通文本消息
|
||||
* Handling of text types
|
||||
* There are currently two cases here, the first is message.type = 'follow_up' means suggestion, the second is plain text message
|
||||
*/
|
||||
if (message.content_type === ContentType.Text) {
|
||||
const { eventCallbacks, mdBoxProps } = props;
|
||||
@@ -174,7 +174,7 @@ export const ContentBox: FC<IContentBoxProps> = props => {
|
||||
}
|
||||
|
||||
/**
|
||||
* FIle类型的内容
|
||||
* FIle type content
|
||||
*/
|
||||
if (message.content_type === ContentType.File && isFileEnable) {
|
||||
const { copywriting, fileAttributeKeys } =
|
||||
@@ -198,7 +198,7 @@ export const ContentBox: FC<IContentBoxProps> = props => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片类型的内容
|
||||
* Image type content
|
||||
*/
|
||||
if (message.content_type === ContentType.Image && isImageEnable) {
|
||||
const { eventCallbacks } = props;
|
||||
@@ -224,7 +224,7 @@ export const ContentBox: FC<IContentBoxProps> = props => {
|
||||
}
|
||||
|
||||
/**
|
||||
* function类型
|
||||
* Function type
|
||||
*/
|
||||
if (message.type === 'function_call' && isSimpleFunctionEnable) {
|
||||
const { copywriting } =
|
||||
@@ -236,7 +236,7 @@ export const ContentBox: FC<IContentBoxProps> = props => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件文字同时发送的多模态消息
|
||||
* Multimodal messages with simultaneous text transmission
|
||||
*/
|
||||
if (
|
||||
message.content_type === ContentType.Mix &&
|
||||
|
||||
@@ -20,7 +20,7 @@ import classNames from 'classnames';
|
||||
import './index.less';
|
||||
|
||||
/**
|
||||
* 套壳组件,默认宽度通栏,用于帮助孤立组件与 message box 保持宽度对齐
|
||||
* Sleeve component, the default width bar, is used to help the isolated component maintain width alignment with the message box
|
||||
*/
|
||||
export const FullWidthAligner = (
|
||||
props: PropsWithChildren<{
|
||||
|
||||
@@ -133,7 +133,7 @@ export const MessageBoxWrap: FC<
|
||||
|
||||
const isHovering = useHover(() => wrapRef.current);
|
||||
|
||||
// 适配移动端 移动端没有hover效果,使用点击交互
|
||||
// Adapt mobile end mobile end has no hover effect, use click interaction
|
||||
const [hoverContentVisible, setHoverContentVisible] =
|
||||
useState<boolean>(false);
|
||||
|
||||
@@ -187,7 +187,7 @@ export const MessageBoxWrap: FC<
|
||||
// chat-uikit-message-box-container__message
|
||||
className="flex-1 max-w-[calc(100%-44px)]"
|
||||
>
|
||||
{/* TODO: 不支持一条消息内渲染多个 content */}
|
||||
{/* TODO: Rendering multiple content within a message is not supported */}
|
||||
<div
|
||||
// chat-uikit-message-box-container__message__message-box
|
||||
className="relative flex flex-col w-fit max-w-full"
|
||||
@@ -293,7 +293,7 @@ export const MessageBoxWrap: FC<
|
||||
{right}
|
||||
</div>
|
||||
</div>
|
||||
{/* 对这个 dom 的样式改动前请先阅读上方的 refreshContainerWidthConditionally 方法 */}
|
||||
{/* Please read the refreshContainerWidthConditionally above before changing the style of this dom */}
|
||||
<div
|
||||
ref={messageFooterRef}
|
||||
// chat-uikit-message-box-container__message__message-box__footer
|
||||
|
||||
@@ -27,30 +27,30 @@ import {
|
||||
import { type UserLabelInfo } from '../user-label';
|
||||
|
||||
/**
|
||||
* 样式主题
|
||||
* @deprecated 考虑替换实现方式,目前使用不够灵活
|
||||
* style theme
|
||||
* @Deprecated Consider alternative implementations, currently not flexible enough
|
||||
*/
|
||||
export type MessageBoxTheme =
|
||||
/** 主题色 */
|
||||
/** theme color */
|
||||
| 'primary'
|
||||
/** 白色背景 */
|
||||
/** White Background */
|
||||
| 'whiteness'
|
||||
/**
|
||||
* 灰色背景
|
||||
* Grey background
|
||||
*/
|
||||
| 'grey'
|
||||
/** home用的炫彩底色 */
|
||||
/** Colorful base colors for home use */
|
||||
| 'colorful'
|
||||
/** 官方通知,有彩色边框 */
|
||||
/** Official notice with colored borders */
|
||||
| 'color-border'
|
||||
/** 官方通知,有彩色边框,但是不留 padding */
|
||||
/** Official notice, with colored borders, but no padding. */
|
||||
| 'color-border-card'
|
||||
| 'border'
|
||||
| 'none';
|
||||
|
||||
interface MessageBoxBasicProps {
|
||||
/**
|
||||
* 用户信息
|
||||
* user information
|
||||
*/
|
||||
senderInfo: {
|
||||
userUniqueName?: string;
|
||||
@@ -60,77 +60,77 @@ interface MessageBoxBasicProps {
|
||||
userLabel?: UserLabelInfo | null;
|
||||
};
|
||||
/**
|
||||
* 消息 id
|
||||
* Message ID
|
||||
*/
|
||||
messageId: string | null;
|
||||
|
||||
showUserInfo?: boolean;
|
||||
/**
|
||||
* 主题
|
||||
* theme
|
||||
*/
|
||||
theme?: MessageBoxTheme;
|
||||
/**
|
||||
* 插入消息的footer
|
||||
* Insert the footer of the message
|
||||
*/
|
||||
renderFooter?: (refreshContainerWidth: () => void) => React.ReactNode;
|
||||
/** 鼠标悬浮时展示的组件 */
|
||||
/** Components displayed while the mouse is hovering */
|
||||
hoverContent?: ReactNode;
|
||||
/**
|
||||
* 左侧插槽
|
||||
* Left Slot
|
||||
*/
|
||||
right?: React.ReactNode;
|
||||
/**
|
||||
* 右上角插槽
|
||||
* Upper right slot
|
||||
*/
|
||||
topRightSlot?: React.ReactNode;
|
||||
getBotInfo: GetBotInfo;
|
||||
/**
|
||||
* 是否是移动端
|
||||
* Is it a mobile end?
|
||||
*/
|
||||
layout?: Layout;
|
||||
classname?: string;
|
||||
|
||||
messageBubbleWrapperClassname?: string;
|
||||
messageBoxWraperClassname?: string; // message box的直接父亲样式
|
||||
messageBubbleClassname?: string; // message消息气泡的样式
|
||||
messageErrorWrapperClassname?: string; // message错误的父亲样式
|
||||
isHoverShowUserInfo?: boolean; // hover的时候是否显示用户详细信息
|
||||
messageBoxWraperClassname?: string; // Direct father style of message box
|
||||
messageBubbleClassname?: string; // Message The style of the message bubble
|
||||
messageErrorWrapperClassname?: string; // Message wrong father style
|
||||
isHoverShowUserInfo?: boolean; // Whether to display user details when hovering
|
||||
|
||||
showBackground?: boolean;
|
||||
/**
|
||||
* 容器动态宽度,用于动态计算图片尺寸
|
||||
* Container dynamic width for dynamically calculating image dimensions
|
||||
*/
|
||||
imageAutoSizeContainerWidth?: number;
|
||||
/**
|
||||
* 是否启用图片自适应模式
|
||||
* Whether to enable picture adaptation mode
|
||||
*/
|
||||
enableImageAutoSize?: boolean;
|
||||
/**
|
||||
* 事件回调
|
||||
* event callback
|
||||
*/
|
||||
eventCallbacks?: IEventCallbacks;
|
||||
/**
|
||||
* 针对 JS Error 的响应
|
||||
* Response to JS Error
|
||||
*/
|
||||
onError?: (error: unknown) => void;
|
||||
}
|
||||
|
||||
/** 只是套壳,内容由 children 呈现 */
|
||||
/** It's just a shell, the content is presented by children */
|
||||
export interface MessageBoxShellProps extends MessageBoxBasicProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/** 含有完整内置实现的 MessageBox */
|
||||
/** MessageBox with full built-in implementation */
|
||||
export interface NormalMessageBoxProps extends MessageBoxBasicProps {
|
||||
/**
|
||||
* 消息体
|
||||
* message body
|
||||
*/
|
||||
message: IMessage;
|
||||
/**
|
||||
* 文件需要用到的必备参数
|
||||
* Required parameters for the file
|
||||
*/
|
||||
contentConfigs?: IContentConfigs;
|
||||
/** 样式主题 */
|
||||
/** style theme */
|
||||
theme?: MessageBoxTheme;
|
||||
footer?: ReactNode;
|
||||
readonly?: boolean;
|
||||
@@ -147,11 +147,11 @@ export interface MessageBoxWrapProps {
|
||||
showUserInfo?: boolean;
|
||||
renderFooter?: (refreshContainerWidth: () => void) => React.ReactNode;
|
||||
|
||||
/** 鼠标悬浮时展示的组件 */
|
||||
/** Components displayed while the mouse is hovering */
|
||||
hoverContent?: React.ReactNode;
|
||||
right?: React.ReactNode;
|
||||
/**
|
||||
* 右上角插槽
|
||||
* Upper right slot
|
||||
*/
|
||||
topRightSlot?: React.ReactNode;
|
||||
messageId: string | null;
|
||||
@@ -160,11 +160,11 @@ export interface MessageBoxWrapProps {
|
||||
contentTime: number | undefined;
|
||||
classname?: string;
|
||||
|
||||
messageBoxWraperClassname?: string; // message box的直接父亲样式
|
||||
messageBubbleClassname?: string; // message消息气泡的样式
|
||||
messageBubbleWrapperClassname?: string; // message消息气泡的父亲样式
|
||||
messageErrorWrapperClassname?: string; // message错误的父亲样式
|
||||
isHoverShowUserInfo?: boolean; // hover的时候是否显示用户详细信息
|
||||
messageBoxWraperClassname?: string; // Direct father style of message box
|
||||
messageBubbleClassname?: string; // Message The style of the message bubble
|
||||
messageBubbleWrapperClassname?: string; // Message message bubble father style
|
||||
messageErrorWrapperClassname?: string; // Message wrong father style
|
||||
isHoverShowUserInfo?: boolean; // Whether to display user details when hovering
|
||||
|
||||
showBackground?: boolean;
|
||||
extendedUserInfo?: {
|
||||
@@ -172,16 +172,16 @@ export interface MessageBoxWrapProps {
|
||||
userUniqueName?: string;
|
||||
};
|
||||
/**
|
||||
* 容器动态宽度,用于动态计算图片尺寸
|
||||
* Container dynamic width for dynamically calculating image dimensions
|
||||
*/
|
||||
imageAutoSizeContainerWidth?: number;
|
||||
/**
|
||||
* 是否启用图片自适应模式
|
||||
* Whether to enable picture adaptation mode
|
||||
*/
|
||||
enableImageAutoSize?: boolean;
|
||||
eventCallbacks?: IEventCallbacks;
|
||||
/**
|
||||
* 针对 JS Error 的响应
|
||||
* Response to JS Error
|
||||
*/
|
||||
onError?: (error: unknown) => void;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export const UserLabel: FC<{
|
||||
);
|
||||
};
|
||||
|
||||
// TODO: 增加 show background 变体
|
||||
// TODO: Added show background variant
|
||||
export const UserName: FC<{
|
||||
userUniqueName?: string;
|
||||
className?: string;
|
||||
|
||||
@@ -206,7 +206,7 @@ const FileCard: FC<IFileCardProps> = props => {
|
||||
<div
|
||||
// chat-uikit-file-card__progress-wrap
|
||||
className={classNames(
|
||||
// TODO: ui 补充进度条颜色
|
||||
// TODO: ui supplement progress bar color
|
||||
'coz-fg-hglt-dim absolute top-0 left-0 w-[280px] h-[72px]',
|
||||
'chat-uikit-file-card-progress-animation',
|
||||
)}
|
||||
|
||||
@@ -24,27 +24,27 @@ import {
|
||||
export interface IFileCardProps {
|
||||
file: IFileInfo;
|
||||
/**
|
||||
* 用于识别成功 / 失败状态的key
|
||||
* Key used to identify success/failure status
|
||||
*/
|
||||
attributeKeys: IFileAttributeKeys;
|
||||
/**
|
||||
* 文案配置
|
||||
* copywriting configuration
|
||||
*/
|
||||
tooltipsCopywriting?: IFileCardTooltipsCopyWritingConfig;
|
||||
/**
|
||||
* 是否只读
|
||||
* Is it read-only?
|
||||
*/
|
||||
readonly?: boolean;
|
||||
/**
|
||||
* 取消上传事件回调
|
||||
* Cancel upload event callback
|
||||
*/
|
||||
onCancel: () => void;
|
||||
/**
|
||||
* 重试上传事件回调
|
||||
* Retry upload event callback
|
||||
*/
|
||||
onRetry: () => void;
|
||||
/**
|
||||
* 拷贝url事件回调
|
||||
* Copy URL event callback
|
||||
*/
|
||||
onCopy: () => void;
|
||||
className?: string;
|
||||
|
||||
@@ -57,7 +57,7 @@ export const FileContent: FC<IProps> = props => {
|
||||
const { content_obj = safeJSONParse(message.content) } = message;
|
||||
|
||||
/**
|
||||
* 判断是否为文件类型的卡片 或者 没有配置file属性config则拒绝使用该卡片
|
||||
* Determine whether it is a card of file type, or refuse to use the card without configuring the file attribute config
|
||||
*/
|
||||
if (
|
||||
!isFile(content_obj) ||
|
||||
@@ -68,21 +68,21 @@ export const FileContent: FC<IProps> = props => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理点击取消上传的事件
|
||||
* Handle the event of clicking Cancel Upload
|
||||
*/
|
||||
const handleCancel = () => {
|
||||
onCancel?.({ message, extra: {} });
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理重试上传的事件
|
||||
* Handling events that retry uploads
|
||||
*/
|
||||
const handleRetry = () => {
|
||||
onRetry?.({ message, extra: {} });
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理拷贝文件地址的事件
|
||||
* Handling events that copy file addresses
|
||||
*/
|
||||
const handleCopy = (fileIndex?: number) => {
|
||||
onCopy?.({ message, extra: { fileIndex } });
|
||||
|
||||
@@ -44,7 +44,7 @@ export interface ContentBoxEvents {
|
||||
}
|
||||
|
||||
export interface BaseContentBoxProps {
|
||||
/** 是否在浏览器视窗内,true:在,false:不在,undefined:未检测 */
|
||||
/** Whether in the browser window, true: in, false: not, undefined: not detected */
|
||||
inView?: boolean;
|
||||
contentBoxEvents?: ContentBoxEvents;
|
||||
}
|
||||
|
||||
@@ -56,21 +56,21 @@ export const FileItemList: FC<FileItemListProps> = ({
|
||||
showBackground,
|
||||
}) => {
|
||||
/**
|
||||
* 处理点击取消上传的事件
|
||||
* Handle the event of clicking Cancel Upload
|
||||
*/
|
||||
const handleCancel = () => {
|
||||
onCancel?.({ message, extra: {} });
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理重试上传的事件
|
||||
* Handling events that retry uploads
|
||||
*/
|
||||
const handleRetry = () => {
|
||||
onRetry?.({ message, extra: {} });
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理拷贝文件地址的事件
|
||||
* Handling events that copy file addresses
|
||||
*/
|
||||
const handleCopy = () => {
|
||||
onCopy?.({ message, extra: {} });
|
||||
|
||||
@@ -60,7 +60,7 @@ export const ImageItemList: FC<ImageItemListProps> = ({
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
// 这里借用了 messageBoxInner 的样式风格
|
||||
// Here we borrow the style of messageBoxInner
|
||||
className={typeSafeMessageBoxInnerVariants({
|
||||
color: 'whiteness',
|
||||
border: null,
|
||||
|
||||
@@ -45,10 +45,10 @@ export type MultimodalContentProps = IImageMessageContentProps &
|
||||
};
|
||||
|
||||
/**
|
||||
* 这个组件并不单纯 实际上并不应该叫 Content
|
||||
* This component is not simple and should not actually be called Content.
|
||||
*/
|
||||
|
||||
// TODO: @liushuoyan 提供开关啊~~
|
||||
// TODO: @liushuoyan provides the switch~~
|
||||
export const MultimodalContent: React.FC<MultimodalContentProps> = ({
|
||||
renderTextContentAddonTop,
|
||||
message,
|
||||
@@ -66,7 +66,7 @@ export const MultimodalContent: React.FC<MultimodalContentProps> = ({
|
||||
}) => {
|
||||
const { content_obj } = message;
|
||||
if (!isMultimodalContentListLike(content_obj)) {
|
||||
// TODO: broke 的消息应该需要加一个统一的兜底和上报
|
||||
// TODO: Broke news should need to add a unified bottom line and report
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ export const TextItemList: FC<TextItemListProps> = ({
|
||||
|
||||
return (
|
||||
/**
|
||||
* TODO: 由于目前设计不支持一条 message 渲染多个 content 这里需要借用一下发送消息的文字气泡背景色
|
||||
* 目前只有用户才能发送 multimodal 消息
|
||||
* TODO: Since the current design does not support one message to render multiple content, you need to borrow the text bubble background color of the sent message.
|
||||
* Currently only users can send multimodal messages
|
||||
*/
|
||||
<div
|
||||
className={typeSafeMessageBoxInnerVariants({
|
||||
|
||||
@@ -61,7 +61,7 @@ const getMentionBotContent = ({
|
||||
mentioned,
|
||||
getBotInfo,
|
||||
}: IPlainTextMessageContentProps) => {
|
||||
// 接口真不一定返回了 mention_list
|
||||
// The interface does not necessarily return mention_list
|
||||
if (!mentioned) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export const SingleImageContentWithAutoSize: FC<
|
||||
Boolean(sth && 'image_list' in { ...sth }),
|
||||
}),
|
||||
} = message;
|
||||
// 类型守卫,一般情况也不影响hooks的顺序问题
|
||||
// Type guards generally do not affect the order of hooks
|
||||
if (!isImage(content_obj)) {
|
||||
return null;
|
||||
}
|
||||
@@ -74,10 +74,10 @@ export const SingleImageContentWithAutoSize: FC<
|
||||
};
|
||||
|
||||
/**
|
||||
* 这里这么做是有原因的
|
||||
* 前端计算groupId是通过replyId分组(服务端未ack前是localMessageId)
|
||||
* 因此服务端ack后会导致循环的key发生变化,导致组件unmount -> mount(销毁重建)
|
||||
* 因此需要用比较trick的方式来实现图片展示优化的问题
|
||||
* There's a reason for this.
|
||||
* The front-end compute groupId is grouped by replyId (localMessageId before server level is not ack)
|
||||
* Therefore, after the server level ack, the key of the loop will change, causing the component to unmount - > mount (destroy and rebuild).
|
||||
* Therefore, it is necessary to use a more trick way to achieve the problem of picture display optimization
|
||||
*/
|
||||
const blobImageMap: IBlobImageMap = {};
|
||||
const isBlob = (url: string) => url?.startsWith('blob:');
|
||||
@@ -89,7 +89,7 @@ const SingleImageContentWithAutoSizeImpl: FC<
|
||||
const { imageAutoSizeContainerWidth = 0 } = useUiKitMessageBoxContext();
|
||||
const localMessageId = message.extra_info.local_message_id;
|
||||
|
||||
// 目前服务端下发的图片 ori = thumb 因此目前用一个就行
|
||||
// The picture sent by the current server level ori = thumb, so just use one for now.
|
||||
const currentImageUrl = content_obj?.image_list?.at(0)?.image_ori?.url ?? '';
|
||||
|
||||
const { displayHeight, displayWidth, isCover } = getImageDisplayAttribute(
|
||||
|
||||
@@ -26,26 +26,26 @@ import './index.less';
|
||||
type IBlobImageMap = Record<string, string>;
|
||||
|
||||
/**
|
||||
* 这里这么做是有原因的
|
||||
* 前端计算groupId是通过replyId分组(服务端未ack前是localMessageId)
|
||||
* 因此服务端ack后会导致循环的key发生变化,导致组件unmount -> mount(销毁重建)
|
||||
* 因此需要用比较trick的方式来实现图片展示优化的问题
|
||||
* There's a reason for this.
|
||||
* The front-end compute groupId is grouped by replyId (localMessageId before server level is not ack)
|
||||
* Therefore, after the server level ack, the key of the loop will change, causing the component to unmount - > mount (destroy and rebuild).
|
||||
* Therefore, it is necessary to use a more trick way to achieve the problem of picture display optimization
|
||||
*/
|
||||
const blobImageMap: IBlobImageMap = {};
|
||||
const isBlob = (url: string) => url?.startsWith('blob:');
|
||||
|
||||
/**
|
||||
* @deprecated 废弃不再维护,请尽快迁移至 SingleImageContentWithAutoSize 组件
|
||||
* @Deprecated is no longer maintained, please migrate to SingleImageContentWithAutoSize component as soon as possible
|
||||
*/
|
||||
export const SingleImageContent: FC<IImageMessageContentProps> = props => {
|
||||
const { message, onImageClick } = props;
|
||||
|
||||
// @liushuoyan 这里类型大溃败,引入了 any
|
||||
// @Liushuoyan here type rout, introduced any
|
||||
const { content_obj = safeJSONParse(message.content) } = message;
|
||||
|
||||
const localMessageId = message.extra_info.local_message_id;
|
||||
|
||||
// 目前服务端下发的图片 ori = thumb 因此目前用一个就行
|
||||
// The picture sent by the current server level ori = thumb, so just use one for now.
|
||||
const currentImageUrl = content_obj?.image_list?.at(0)?.image_ori?.url ?? '';
|
||||
|
||||
if (isBlob(currentImageUrl)) {
|
||||
|
||||
@@ -42,7 +42,7 @@ export const SingleImageContentUI: React.FC<SingleImageContentUIProps> = ({
|
||||
src={thumbUrl || EmptyImage}
|
||||
className="chat-uikit-single-image-content__image"
|
||||
/**
|
||||
* 这里不采用 semi Image 组件自带的 preview 功能。传入的 onImageClick 回调中有副作用会拉起 preview 组件
|
||||
* The preview function that comes with the semi Image component is not used here. There are side effects in the incoming onImageClick callback that will pull the preview component
|
||||
*/
|
||||
preview={false}
|
||||
/>
|
||||
|
||||
@@ -55,7 +55,7 @@ export type CozeImageProps = MdBoxImageProps & {
|
||||
|
||||
const TIME_OUT = 10000;
|
||||
|
||||
// TODO: @liushuoyan 看看这里能不能搞一个插槽之类的东西
|
||||
// TODO: @liushuoyan see if you can get a slot or something here
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention, @coze-arch/max-line-per-function
|
||||
export const _CozeImage: FC<CozeImageProps> = props => {
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
@@ -110,7 +110,7 @@ export const _CozeImage: FC<CozeImageProps> = props => {
|
||||
const originImageInfoRef = useRef<OriginImageInfo>({});
|
||||
const [imageStyles, setImageStyles] = useState<CSSProperties>({});
|
||||
|
||||
// 加载状态
|
||||
// loading status
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [imageSrc, setImageSrc] = useState(DefaultImage);
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ const isCocoLink = (link: string) => {
|
||||
return parsedLink.protocol === 'coco:';
|
||||
};
|
||||
|
||||
/** 被链接元素替换成的组件 */
|
||||
/** Components replaced by linked elements */
|
||||
export const CozeLink: FC<
|
||||
MdBoxLinkProps & {
|
||||
onLinkElementEnter?: (params: {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { createContext, useContext } from 'react';
|
||||
|
||||
import { type IEventCallbacks } from '@coze-common/chat-uikit-shared';
|
||||
/**
|
||||
* 为了支持CozeImage隔空取物和性能优化角度考虑临时开的Context,没事儿别乱用。。。。
|
||||
* In order to support CozeImage's empty fetching and performance optimization, consider the temporarily opened Context, don't use it indiscriminately...
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const OnboardingContext = createContext<{
|
||||
|
||||
@@ -101,7 +101,7 @@ export const useAudioRecordInteraction = ({
|
||||
return;
|
||||
}
|
||||
const { clientX, clientY } = getClientPosition(eventType);
|
||||
// 获取元素的边界信息
|
||||
// Get the boundary information of the element
|
||||
const rect = activeZoneElement.getBoundingClientRect();
|
||||
const isOutRect =
|
||||
clientX < rect.left ||
|
||||
@@ -109,7 +109,7 @@ export const useAudioRecordInteraction = ({
|
||||
clientY < rect.top ||
|
||||
clientY > rect.bottom;
|
||||
|
||||
// 判断触摸点是否在元素范围内
|
||||
// Determine whether the touch point is within the scope of the element
|
||||
if (isOutRect && !isMoveLeave.current) {
|
||||
isMoveLeave.current = true;
|
||||
onMoveLeaveRef.current?.();
|
||||
@@ -201,7 +201,7 @@ export const useAudioRecordInteraction = ({
|
||||
});
|
||||
useEffect(
|
||||
() => () => {
|
||||
// 避免异常情况下事件无法卸载
|
||||
// Avoid events that cannot be uninstalled under abnormal circumstances
|
||||
document.removeEventListener('mousemove', onPointerMove);
|
||||
document.removeEventListener('touchmove', onPointerMove);
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@ export const useObserveCardContainer = ({
|
||||
}) => {
|
||||
const eventCenter = useUiKitEventCenter();
|
||||
|
||||
/** 30s 内没变化则自动清除 observer */
|
||||
/** If there is no change within 30s, the observer will be automatically cleared. */
|
||||
const debouncedDisconnect = useDebounceFn(
|
||||
(getResizeObserver: () => ResizeObserver | null) => {
|
||||
const resizeObserver = getResizeObserver();
|
||||
|
||||
@@ -20,7 +20,7 @@ export const formatMessageBoxContentTime = (contentTime: number): string => {
|
||||
if (contentTime < 1) {
|
||||
return '';
|
||||
}
|
||||
// 当天:hh:mm;跨天:mm-dd hh:mm;跨年:yyyy-mm-dd hh:mm
|
||||
// Day: hh: mm; across the sky: mm-dd hh: mm; New Year's Eve: yyyy-mm-dd hh: mm
|
||||
const now = Date.now();
|
||||
const today = dayjs(now);
|
||||
const messageDay = dayjs(contentTime);
|
||||
|
||||
@@ -19,17 +19,17 @@ export const getImageDisplayAttribute = (
|
||||
height: number,
|
||||
contentWidth: number,
|
||||
) => {
|
||||
// 图片比例
|
||||
// image scale
|
||||
const imageRatio = width / height;
|
||||
|
||||
// 展示宽度
|
||||
// display width
|
||||
let displayWidth = contentWidth;
|
||||
// 展示高度
|
||||
// display height
|
||||
let displayHeight = contentWidth / imageRatio;
|
||||
// 是否裁切
|
||||
// Whether to cut
|
||||
let isCover = false;
|
||||
|
||||
// (小尺寸图)
|
||||
// (Small size drawing)
|
||||
|
||||
if (width <= contentWidth && height <= 240) {
|
||||
displayWidth = width;
|
||||
@@ -38,16 +38,16 @@ export const getImageDisplayAttribute = (
|
||||
displayWidth = contentWidth;
|
||||
displayHeight = 120;
|
||||
isCover = true;
|
||||
// (长竖图)图片宽度:图片高度 <= 0.5
|
||||
// (Long vertical) Image width: Image height < = 0.5
|
||||
} else if (imageRatio <= 0.5) {
|
||||
displayWidth = 120;
|
||||
displayHeight = 240;
|
||||
isCover = true;
|
||||
// (等比展示图)
|
||||
// (Equivalent display picture)
|
||||
} else if (0.5 <= imageRatio && imageRatio <= contentWidth / 240) {
|
||||
displayWidth = 240 * imageRatio;
|
||||
displayHeight = 240;
|
||||
// (中长横图)
|
||||
// (Medium and long horizontal chart)
|
||||
} else if (
|
||||
contentWidth / 240 <= imageRatio &&
|
||||
imageRatio <= contentWidth / 240
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* 是否是在苹果平台的Webkit内核浏览器下,
|
||||
* 注:这个判断条件不等于是在苹果设备下,因为部分苹果设备(例如Mac)可以运行非原生Webkit引擎的浏览器,例如Chromium(Blink)
|
||||
* Is it under the Webkit kernel browser of the Apple platform?
|
||||
* Note: This judgment condition is not equal to under Apple devices, because some Apple devices (such as Mac) can run non-native Webkit engine browsers, such as Chromium (Blink)
|
||||
*/
|
||||
export const isAppleWebkit = () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -26,5 +26,5 @@ type StoreStructRange = Record<keyof StoreStruct, boolean | string | number>;
|
||||
|
||||
const storeStructRangeCheck = (range: StoreStructRange) => 0;
|
||||
declare const voidStruct: StoreStruct;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- 类型测试
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- type testing
|
||||
const dryRun = () => storeStructRangeCheck(voidStruct);
|
||||
|
||||
@@ -18,8 +18,8 @@ import Browser from 'bowser';
|
||||
|
||||
let getIsMobileCache: boolean | undefined;
|
||||
/**
|
||||
* 是否是移动设备
|
||||
* 注:ipad 不是移动设备
|
||||
* Is it a mobile device?
|
||||
* Note: iPad is not a mobile device
|
||||
*/
|
||||
const isMobile = () =>
|
||||
Browser.getParser(navigator.userAgent)
|
||||
@@ -35,7 +35,7 @@ export const getIsMobile = () => {
|
||||
|
||||
let getIsIPhoneOrIPadCache: boolean | undefined;
|
||||
/**
|
||||
* gpt-4 提供的代码
|
||||
* Code provided by gpt-4
|
||||
*/
|
||||
export const getIsIPhoneOrIPad = () => {
|
||||
if (typeof getIsIPhoneOrIPadCache === 'undefined') {
|
||||
@@ -53,7 +53,7 @@ export const getIsIPhoneOrIPad = () => {
|
||||
|
||||
let getIsIPadCache: boolean | undefined;
|
||||
/**
|
||||
* gpt-4 提供的代码
|
||||
* Code provided by gpt-4
|
||||
*/
|
||||
export const getIsIPad = () => {
|
||||
if (typeof getIsIPadCache === 'undefined') {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated 非常非常坏,尽快换为 typeSafeJsonParse
|
||||
* @Deprecated very very bad, change to typeSafeJsonParse ASAP.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const safeJSONParse: (v: any, emptyValue?: any) => any = (
|
||||
|
||||
Reference in New Issue
Block a user