/* * 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 ForwardedRef, forwardRef, useEffect, useImperativeHandle, useRef, useState, } from 'react'; import cls from 'classnames'; import { useEditor } from '@coze-editor/editor/react'; import { type EditorAPI } from '@coze-editor/editor/preset-prompt'; import { I18n } from '@coze-arch/i18n'; import { IconCozArrowRightFill } from '@coze-arch/coze-design/icons'; import { Tabs, TabPane, Button } from '@coze-arch/coze-design'; import { insertToNewline, type PromptContextInfo, } from '@coze-common/prompt-kit-base/shared'; import { RecommendCardLoading } from '../recommend-card/card-loading'; import { ViewAll, RecommendCard } from '../recommend-card'; import { useGetLibrarys } from '../hooks/use-get-librarys'; import { useScrollControl } from '../hooks/use-case/use-scroll-control'; import { usePromptLibraryModal } from '../../prompt-library'; import { EmptyRecommend } from './empty'; import styles from './index.module.less'; import '@coze-common/prompt-kit-base/shared/css'; import { LeftScrollButton, RightScrollButton } from './scroll-button'; const LIMIT_LIBRARY_SIZE = 6; type TabType = 'Recommended' | 'Team'; const getTabLabelMap = (isPersonal: boolean) => ({ Recommended: I18n.t('prompt_resource_recommended'), Team: isPersonal ? I18n.t('prompt_resource_personal') : I18n.t('prompt_resource_team'), }); interface ActionExtraInfo { id: string; category: string; } interface RecommendPannelProps { className?: string; cardClassName?: string; listContainerClassName?: string; tabs: TabType[]; /** For event tracking: page source */ source: string; importPromptWhenEmpty?: string; spaceId: string; /** For event tracking: bot_id */ botId?: string; /** For event tracking: project_id */ projectId?: string; /** For event tracking: workflow_id */ workflowId?: string; isPersonal?: boolean; enableLibrary?: boolean; getConversationId?: () => string | undefined; getPromptContextInfo?: () => PromptContextInfo; onInsertPrompt?: (prompt: string, info?: ActionExtraInfo) => void; onUpdateSuccess?: ( mode: 'create' | 'edit' | 'info', info: ActionExtraInfo, ) => void; onCopyPrompt?: (info: ActionExtraInfo) => void; onDeletePrompt?: (info: ActionExtraInfo) => void; ref: ForwardedRef; } /* eslint-disable @coze-arch/max-line-per-function */ export const Index = (props: RecommendPannelProps) => { const domRef = useRef(null); const { className, cardClassName, listContainerClassName, onInsertPrompt, tabs, spaceId, enableLibrary = false, getConversationId, getPromptContextInfo, importPromptWhenEmpty, source, botId, projectId, workflowId, ref, isPersonal = false, onCopyPrompt, onDeletePrompt, onUpdateSuccess, } = props; const [activeTab, setActiveTab] = useState<(typeof tabs)[number]>(tabs[0]); const editor = useEditor(); const handleInsertPrompt = async (prompt: string, id: string) => { const insertPrompt = await insertToNewline({ editor, prompt }); onInsertPrompt?.(insertPrompt, { id, category: activeTab }); }; const { loading, data, runAsync } = useGetLibrarys(); const isEmpty = !loading && data?.[activeTab]?.length === 0; const { open, node: PromptLibrary } = usePromptLibraryModal({ spaceId, getConversationId, editor, isPersonal, source, botId, projectId, workflowId, getPromptContextInfo, importPromptWhenEmpty, onInsertPrompt, onUpdateSuccess: (mode, selectedLibrary) => { runAsync(activeTab, { space_id: spaceId, size: LIMIT_LIBRARY_SIZE, }); onUpdateSuccess?.(mode, selectedLibrary); }, onCopyPrompt, onDeletePrompt, }); useEffect(() => { if (!spaceId) { return; } runAsync(activeTab, { space_id: spaceId, size: LIMIT_LIBRARY_SIZE, }); }, [spaceId, activeTab]); const { scrollRefs, canScrollLeft, canScrollRight, handleScroll } = useScrollControl({ activeTab, tabs, loading, data, }); useImperativeHandle(ref, () => ({ refresh: (tab: 'Recommended' | 'Team') => { runAsync(tab, { space_id: spaceId, size: LIMIT_LIBRARY_SIZE, }); }, })); return (
{ if (typeof ref === 'function') { ref(null); } domRef.current = el; }} className={cls( styles['recommend-pannel'], 'flex flex-col justify-between w-full', 'absolute bottom-0 left-0 right-0', 'py-3 px-5', className, )} > setActiveTab(key as (typeof tabs)[number])} tabBarExtraContent={ enableLibrary ? (
open({ defaultActiveTab: activeTab })} >
) : null } > {tabs.map((item, index) => ( {canScrollLeft ? ( handleScroll('left')} /> ) : null}
(scrollRefs.current[index] = el)} className={cls( 'relative overflow-x-auto styled-scrollbar h-[120px] box-content hover-show-scrollbar', 'flex-1', listContainerClassName, )} > {isEmpty ? ( ) : (
{loading ? Array.from({ length: LIMIT_LIBRARY_SIZE }).map( (_, _index) => , ) : null} {data?.[item]?.map((card, _index) => ( handleInsertPrompt(prompt, card.id) } /> ))} open({ defaultActiveTab: item })} />
)}
{canScrollRight ? ( handleScroll('right')} /> ) : null}
))}
{PromptLibrary}
); }; interface RecommendPannelRef { refresh: (tab: 'Recommended' | 'Team') => void; } export const RecommendPannel = forwardRef< RecommendPannelRef, RecommendPannelProps >((props, ref) => );