Files
coze-studio/frontend/packages/common/prompt-kit/main/src/prompt-recommend/recommend-card/index.tsx
fanlv 890153324f feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
2025-07-20 17:36:12 +08:00

198 lines
5.6 KiB
TypeScript

/*
* 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 { useEffect, useState } from 'react';
import cls from 'classnames';
import { useEditor } from '@coze-editor/editor/react';
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
import { Popover, Button, Typography } from '@coze-arch/coze-design';
import { PlaygroundApi } from '@coze-arch/bot-api';
import { ThemeExtension } from '@coze-common/editor-plugins/theme';
import { LibraryBlockWidget } from '@coze-common/editor-plugins/library-insert';
import { InputSlotWidget } from '@coze-common/editor-plugins/input-slot';
import {
PromptEditorRender,
PromptEditorProvider,
} from '@coze-common/prompt-kit-base/editor';
import { I18n } from '@coze-arch/i18n';
import { EditorView } from '@codemirror/view';
import '@coze-common/prompt-kit-base/shared/css';
interface RecommendCardProps {
id: string;
title: string;
description: string;
position?: 'topLeft' | 'top';
prompt?: string;
spaceId: string;
onInsertPrompt?: (prompt: string) => void;
className?: string;
}
export const RecommendCard = (props: RecommendCardProps) => {
const {
id,
title,
description,
prompt,
onInsertPrompt,
spaceId,
className,
position,
} = props;
const [promptText, setPromptText] = useState(prompt ?? '');
const [isPopoverVisible, setIsPopoverVisible] = useState(false);
useEffect(() => {
if (prompt) {
return;
}
PlaygroundApi.GetPromptResourceInfo({
prompt_resource_id: id,
}).then(({ data: { prompt_text } = {} }) => {
setPromptText(prompt_text ?? '');
});
}, [prompt, id]);
return (
<PromptEditorProvider>
<Popover
position={position}
visible={isPopoverVisible}
onVisibleChange={setIsPopoverVisible}
trigger="hover"
key={id}
className="rounded"
showArrow
// mouseLeaveDelay={150}
// mouseEnterDelay={150}
autoAdjustOverflow
content={
isPopoverVisible ? (
<UsePromptPopoverContent
prompt={promptText}
title={title}
spaceId={spaceId}
onInsertPrompt={value => {
onInsertPrompt?.(value);
setIsPopoverVisible(false);
}}
/>
) : null
}
>
<div
className={cls(
'flex flex-col flex-shrink-0 flex-nowrap gap-1 px-3 py-2 relative',
'aspect-[180/120] overflow-hidden',
'rounded-lg border coz-stroke-primary coz-bg-max cursor-pointer',
'coz-stroke-primary border-[0.5px] border-solid',
'hover:coz-mg-secondary-hovered',
className,
)}
>
<Typography.Text
className="font-medium text-lg"
ellipsis={{ rows: 1 }}
>
{title}
</Typography.Text>
<Typography.Text className="text-base" ellipsis={{ rows: 3 }}>
{description ?? prompt?.slice(0, 50)}
</Typography.Text>
</div>
</Popover>
</PromptEditorProvider>
);
};
const UsePromptPopoverContent: React.FC<{
prompt?: string;
title: string;
spaceId: string;
onInsertPrompt?: (prompt: string) => void;
}> = ({ prompt = '', title, spaceId, onInsertPrompt }) => {
const editor = useEditor<EditorAPI>();
useEffect(() => {
editor?.$view.dispatch({
changes: {
from: 0,
to: editor?.$view.state.doc.length,
insert: prompt,
},
});
}, [editor, prompt]);
return (
<div className="flex flex-col justify-between w-[300px] h-[300px] gap-3">
<div className="flex flex-col gap-1 overflow-y-auto styled-scrollbar hover-show-scrollbar">
<div className="text-sm font-medium coz-fg-primary">{title}</div>
<PromptEditorRender defaultValue={prompt} readonly />
<InputSlotWidget mode="input" />
<LibraryBlockWidget librarys={[]} readonly spaceId={spaceId} />
<ThemeExtension
themes={[
EditorView.theme({
'.cm-line': {
paddingLeft: '0 !important',
},
}),
]}
/>
</div>
<div className="coz-mg-hglt hover:!coz-mg-hglt-hovered rounded">
<Button
color="primary"
className="w-full font-sm font-medium !bg-transparent !coz-fg-hglt "
onClick={() => {
onInsertPrompt?.(prompt);
}}
>
{I18n.t('prompt_resource_insert_prompt')}
</Button>
</div>
</div>
);
};
export const ViewAll = ({
onClick,
className,
}: {
onClick: () => void;
className?: string;
}) => (
<div
onClick={onClick}
className={cls(
'flex flex-col flex-shrink-0 flex-nowrap gap-1 px-3 py-2 items-center justify-center',
'aspect-[180/120]',
'rounded-lg border coz-stroke-primary coz-bg-max cursor-pointer text-sm',
'coz-stroke-primary border-[0.5px] border-solid',
'hover:coz-mg-secondary-hovered',
className,
)}
>
<div className="coz-fg-primary font-medium">
{I18n.t('prompt_resource_view_all')}
</div>
</div>
);