import classnames from 'classnames'; import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import type { DirectionType } from 'antd/es/config-provider'; import pickAttrs from 'rc-util/lib/pickAttrs'; import type { Conversation } from './interface'; import { SparkMoreLine } from '@agentscope-ai/icons'; import { Button, Checkbox, IconButton, Popover } from '@agentscope-ai/design'; import { useInViewport } from 'ahooks'; export interface ConversationsItemProps extends Omit, 'onClick' | 'onSelect'> { info: Conversation; prefixCls?: string; direction?: DirectionType; menu?: { label?: string; key?: string; icon?: React.ReactNode; danger?: boolean; onClick?: (info: Conversation) => void; onEdit?: (label: string, session: Conversation) => Promise; disabled?: boolean; }[]; active?: boolean; selectable?: boolean; selected?: boolean; onSelect?: (key: string, selected: boolean) => void; onClick?: (info: Conversation) => void; } const editableMap = {}; export function useEditable(id) { const [editable, setEditable] = useState(editableMap[id]); return [editable, (value) => { for (const key in editableMap) { editableMap[key] = false; } editableMap[id] = value; setEditable(value); }] } const ConversationsItem: React.FC = React.memo((props) => { const [editable, setEditable] = useEditable(props.info.key); const [popoverVisible, setPopoverVisible] = useState(false); const { prefixCls, info, className, direction, onClick, active, selectable, selected, onSelect, menu, ...restProps } = props; const domProps = pickAttrs(restProps, { aria: true, data: true, attr: true, }); const ref = useRef(null); const [inViewport] = useInViewport(ref); const { disabled } = info; const mergedCls = classnames( className, `${prefixCls}-item`, { [`${prefixCls}-item-active`]: active && !disabled }, { [`${prefixCls}-item-disabled`]: disabled }, { [`${prefixCls}-item-timeline`]: info.timeline || selectable }, ); const onInternalClick: React.MouseEventHandler = () => { if (selectable) { return onSelect?.(info.key, !selected); } if (!disabled && onClick) { return onClick(info); } }; return (
  • {inViewport && info.icon &&
    {info.icon}
    } { inViewport &&
    { (info.timeline || selectable) &&
    { selectable ?
    e.stopPropagation()}> onSelect?.(info.key, !selected)} />
    :
    }
    }
    } placement='bottom'> } disabled={disabled} className={`${prefixCls}-menu-icon`} onClick={e => e.stopPropagation()} /> ) }
    { info.desc &&
    {info.desc}
    }
    }
  • ); }); function Label(props) { const { editable, prefixCls, info, setEditable, onEdit } = props; const [label, setLabel] = useState(info.label); const [prevInfoLabel, setPrevInfoLabel] = useState(info.label); if (info.label !== prevInfoLabel) { setPrevInfoLabel(info.label); setLabel(info.label); } return editable ? { if (v === label) return setEditable(false); onEdit(v, info)?.then(() => { setLabel(v); }).catch(() => { setLabel(label); }).finally(() => { setEditable(false); }); }} setEditable={setEditable} /> :
    {label}
    } function Input({ prefixCls, value, onBlur, setEditable }) { const [v, sv] = useState(value); const ref = useRef(); useEffect(() => { ref.current.focus(); }, []); useEffect(() => { sv(value); }, [value]) return e.stopPropagation()} onChange={e => sv(e.target.value)} onBlur={() => onBlur(v)} />; } function MenuItem(props) { const { label, icon, danger, info, disabled } = props; const onClick = (e) => { if (disabled) return; e.stopPropagation(); props.onClick?.(info); }; if (icon && label) return ; if (icon) return ; if (label) return ; return null; } export default ConversationsItem;