feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Row, Col } from '@coze-arch/coze-design';
|
||||
import { Image } from '@coze-arch/bot-md-box-adapter/slots';
|
||||
import {
|
||||
type OnImageClickCallback,
|
||||
type OnImageRenderCallback,
|
||||
} from '@coze-arch/bot-md-box-adapter';
|
||||
|
||||
import './index.less';
|
||||
|
||||
export enum CompressAlgorithm {
|
||||
None = 0,
|
||||
Snappy = 1,
|
||||
Zstd = 2,
|
||||
}
|
||||
export interface MsgContentData {
|
||||
card_data?: string;
|
||||
compress?: CompressAlgorithm;
|
||||
}
|
||||
|
||||
export interface ContentBoxEvents {
|
||||
onError?: (err: unknown) => void;
|
||||
onLoadStart?: () => void;
|
||||
onLoadEnd?: () => void;
|
||||
onLoad?: () => Promise<MsgContentData | undefined>;
|
||||
}
|
||||
|
||||
export interface BaseContentBoxProps {
|
||||
/** 是否在浏览器视窗内,true:在,false:不在,undefined:未检测 */
|
||||
inView?: boolean;
|
||||
contentBoxEvents?: ContentBoxEvents;
|
||||
}
|
||||
|
||||
export interface ImageMessageContent {
|
||||
key: string;
|
||||
image_thumb: {
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
image_ori: {
|
||||
url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
request_id?: string;
|
||||
}
|
||||
|
||||
export interface ImageContent {
|
||||
image_list: ImageMessageContent[];
|
||||
}
|
||||
|
||||
export interface ImageBoxProps extends BaseContentBoxProps {
|
||||
data: ImageContent;
|
||||
eventCallbacks?: {
|
||||
onImageClick?: OnImageClickCallback;
|
||||
onImageRender?: OnImageRenderCallback;
|
||||
};
|
||||
}
|
||||
const getImageBoxGutterAndSpan = (
|
||||
length: number,
|
||||
): {
|
||||
gutter: React.ComponentProps<typeof Row>['gutter'];
|
||||
span: React.ComponentProps<typeof Col>['span'];
|
||||
} => {
|
||||
if (length === 1) {
|
||||
return { gutter: [1, 1], span: 24 };
|
||||
}
|
||||
return { gutter: [2, 2], span: 12 };
|
||||
};
|
||||
|
||||
export const ImageBox: FC<ImageBoxProps> = ({ data, eventCallbacks }) => {
|
||||
const { onImageClick, onImageRender } = eventCallbacks || {};
|
||||
const { image_list = [] } = data || {};
|
||||
|
||||
const layout = getImageBoxGutterAndSpan(image_list?.length);
|
||||
|
||||
return (
|
||||
<div className={classNames('chat-uikit-image-box', 'rounded-normal')}>
|
||||
<Row gutter={layout.gutter}>
|
||||
{image_list.map(({ image_thumb }, index) => (
|
||||
<Col span={layout.span} key={index}>
|
||||
<Image
|
||||
onImageClick={onImageClick}
|
||||
onImageRender={onImageRender}
|
||||
src={image_thumb.url}
|
||||
imageOptions={{
|
||||
squareContainer: true,
|
||||
}}
|
||||
className="object-cover"
|
||||
/>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
.chat-uikit-image-content {
|
||||
user-select: none;
|
||||
width: 240px;
|
||||
|
||||
img {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-uikit-image-error-boundary {
|
||||
>.semi-image {
|
||||
>img {
|
||||
width: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.chat-uikit-image-box {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 { ErrorBoundary } from 'react-error-boundary';
|
||||
import { type FC } from 'react';
|
||||
|
||||
import { Image } from '@coze-arch/coze-design';
|
||||
import {
|
||||
type IOnImageClickParams,
|
||||
type IBaseContentProps,
|
||||
} from '@coze-common/chat-uikit-shared';
|
||||
|
||||
import { safeJSONParse } from '../../../utils/safe-json-parse';
|
||||
import { isImage } from '../../../utils/is-image';
|
||||
import defaultImage from '../../../assets/image-empty.png';
|
||||
import { ImageBox } from './image-box';
|
||||
|
||||
import './index.less';
|
||||
|
||||
export type IImageMessageContentProps = IBaseContentProps & {
|
||||
onImageClick?: (params: IOnImageClickParams) => void;
|
||||
};
|
||||
|
||||
export const ImageContentImpl: FC<IImageMessageContentProps> = props => {
|
||||
const { message, onImageClick } = props;
|
||||
|
||||
const { content_obj = safeJSONParse(message.content) } = message;
|
||||
|
||||
if (!isImage(content_obj)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="chat-uikit-image-content">
|
||||
<ImageBox
|
||||
data={{
|
||||
image_list: content_obj?.image_list ?? [],
|
||||
}}
|
||||
eventCallbacks={{
|
||||
onImageClick: (e, eventData) => {
|
||||
onImageClick?.({
|
||||
message,
|
||||
extra: { url: eventData.src as string },
|
||||
});
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ImageContentImpl.displayName = 'ImageContentImpl';
|
||||
|
||||
export const ImageContent: FC<IImageMessageContentProps> = props => (
|
||||
<ErrorBoundary
|
||||
fallback={
|
||||
<div className="chat-uikit-image-error-boundary">
|
||||
<Image src={defaultImage} preview={false} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ImageContentImpl {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
ImageContent.displayName = 'ImageContent';
|
||||
Reference in New Issue
Block a user