Merge branch 'deploy/agent-dev' of github.com:langgenius/dify into deploy/agent-dev

This commit is contained in:
hjlarry
2026-03-27 17:10:12 +08:00
7 changed files with 46 additions and 50 deletions

View File

@@ -1,21 +1,17 @@
import type { LexicalNode } from 'lexical'
import type { Dispatch, SetStateAction } from 'react'
import {
flip,
offset,
shift,
useFloating,
} from '@floating-ui/react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { LexicalTypeaheadMenuPlugin, MenuOption } from '@lexical/react/LexicalTypeaheadMenuPlugin'
import {
$insertNodes,
} from 'lexical'
import * as React from 'react'
import { useCallback, useLayoutEffect, useMemo } from 'react'
import ReactDOM from 'react-dom'
import { useCallback, useMemo } from 'react'
import { useBasicTypeaheadTriggerMatch } from '@/app/components/base/prompt-editor/hooks'
import { $splitNodeContainingQuery } from '@/app/components/base/prompt-editor/utils'
import {
Popover,
PopoverContent,
} from '@/app/components/base/ui/popover'
import { FilePickerPanel } from './file-picker-panel'
import { $createFileReferenceNode } from './file-reference-block/node'
@@ -25,29 +21,8 @@ class FilePickerMenuOption extends MenuOption {
}
}
type ReferenceSyncProps = {
anchor: HTMLElement | null
setReference: Dispatch<SetStateAction<HTMLElement | null>> | ((node: HTMLElement | null) => void)
}
const ReferenceSync = ({ anchor, setReference }: ReferenceSyncProps) => {
useLayoutEffect(() => {
setReference(anchor)
}, [anchor, setReference])
return null
}
const FilePickerBlock = () => {
const [editor] = useLexicalComposerContext()
const { refs, floatingStyles, isPositioned } = useFloating({
placement: 'bottom-start',
middleware: [
offset(0),
shift({ padding: 8 }),
flip(),
],
})
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
minLength: 0,
maxLength: 0,
@@ -76,16 +51,20 @@ const FilePickerBlock = () => {
const closeMenu = () => selectOptionAndCleanUp(options[0])
return ReactDOM.createPortal(
<>
<ReferenceSync anchor={anchorElementRef.current} setReference={refs.setReference} />
<div
ref={refs.setFloating}
style={{
...floatingStyles,
visibility: isPositioned ? 'visible' : 'hidden',
}}
className="z-[1002] outline-none"
return (
<Popover
open
onOpenChange={(open) => {
if (!open)
closeMenu()
}}
>
<PopoverContent
placement="bottom-start"
sideOffset={4}
popupClassName="rounded-none border-none bg-transparent shadow-none"
positionerProps={{ anchor: anchorElementRef }}
popupProps={{ initialFocus: false, finalFocus: false }}
>
<FilePickerPanel
onSelectNode={(node) => {
@@ -93,11 +72,10 @@ const FilePickerBlock = () => {
closeMenu()
}}
/>
</div>
</>,
anchorElementRef.current,
</PopoverContent>
</Popover>
)
}, [floatingStyles, insertFileReference, isPositioned, options, refs])
}, [insertFileReference, options])
return (
<LexicalTypeaheadMenuPlugin

View File

@@ -77,7 +77,9 @@ describe('FileReferenceBlock', () => {
)
await act(async () => {
fireEvent.mouseDown(screen.getByText('contract.pdf'))
const target = screen.getByText('contract.pdf')
fireEvent.mouseDown(target)
fireEvent.click(target)
})
expect(await screen.findByText('workflow.skillEditor.referenceFiles')).toBeInTheDocument()

View File

@@ -163,7 +163,21 @@ const FileReferenceBlock = ({ nodeKey, resourceId }: FileReferenceBlockProps) =>
const fileBlock = (
<Popover
open={open}
onOpenChange={setOpen}
onOpenChange={(nextOpen, eventDetails) => {
if (!nextOpen && eventDetails.reason === 'focus-out')
return
if (
!nextOpen
&& eventDetails.reason === 'outside-press'
&& eventDetails.event.target instanceof Node
&& ref.current?.contains(eventDetails.event.target)
) {
return
}
setOpen(nextOpen)
}}
>
<div ref={ref} className="inline-flex">
<Tooltip>

View File

@@ -46,7 +46,7 @@ describe('FileTabItem', () => {
render(<FileTabItem {...props} />)
expect(screen.getByText('readme.md')).toHaveClass('italic')
expect(screen.getByText('readme.md')).toHaveClass('italic', 'pr-[0.5px]')
})
})

View File

@@ -74,7 +74,7 @@ const FileTabItem = ({
<span
className={cn(
'max-w-40 truncate text-[13px] font-normal leading-4',
isPreview && 'italic',
isPreview && 'pr-[0.5px] italic',
isActive
? 'text-text-primary'
: 'text-text-tertiary',

View File

@@ -210,7 +210,9 @@ describe('FileTabs', () => {
render(<FileTabs />)
fireEvent.click(screen.getByRole('button', { name: /common\.operation\.close/i }))
fireEvent.click(screen.getByRole('button', { name: /workflow\.skillSidebar\.unsavedChanges\.confirmClose/i }))
const confirmButton = screen.getByRole('button', { name: /workflow\.skillSidebar\.unsavedChanges\.confirmClose/i })
expect(confirmButton.className).toContain('btn-destructive')
fireEvent.click(confirmButton)
expect(mocks.closeTab).toHaveBeenCalledTimes(1)
expect(mocks.closeTab).toHaveBeenCalledWith('file-1')

View File

@@ -134,7 +134,7 @@ const FileTabs = () => {
<AlertDialogCancelButton>
{t('operation.cancel', { ns: 'common' })}
</AlertDialogCancelButton>
<AlertDialogConfirmButton destructive={false} onClick={handleConfirmClose}>
<AlertDialogConfirmButton onClick={handleConfirmClose}>
{t('skillSidebar.unsavedChanges.confirmClose')}
</AlertDialogConfirmButton>
</AlertDialogActions>