mirror of
https://github.com/langgenius/dify.git
synced 2026-07-25 21:48:30 +08:00
fix: smooth the preview-to-build transition (#39495)
This commit is contained in:
parent
aef1c67721
commit
6889974c05
@ -370,6 +370,7 @@ vi.mock('../components/preview/build-chat', async () => {
|
||||
clearChatList?: boolean
|
||||
conversationId?: string | null
|
||||
controllerRef?: Ref<{ stop: () => void }>
|
||||
disabled?: boolean
|
||||
onConversationComplete?: (conversationId: string) => void
|
||||
onConversationIdChange?: (conversationId: string) => void
|
||||
onBeforeSpeechToText?: () => Promise<unknown>
|
||||
@ -397,6 +398,7 @@ vi.mock('../components/preview/build-chat', async () => {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={props.disabled}
|
||||
onClick={() => {
|
||||
void props
|
||||
.onSaveDraftBeforeRun?.()
|
||||
@ -755,6 +757,58 @@ describe('AgentConfigurePage', () => {
|
||||
expect(urlUpdate?.searchParams.get('source')).toBe('shared-link')
|
||||
})
|
||||
|
||||
it('should show an editable composer and an empty Build chat while starting a fresh Build session', async () => {
|
||||
const user = userEvent.setup()
|
||||
const refreshBuildConversation = createDeferredPromise<{
|
||||
debug_conversation_has_messages: boolean
|
||||
debug_conversation_id: string
|
||||
debug_conversation_message_count: number
|
||||
}>()
|
||||
mocks.refreshDebugConversation.mockReturnValueOnce(refreshBuildConversation.promise)
|
||||
mocks.queryState.composer = {
|
||||
data: {
|
||||
agent_soul: {
|
||||
prompt: {
|
||||
system_prompt: 'draft prompt',
|
||||
},
|
||||
},
|
||||
},
|
||||
isFetching: false,
|
||||
isError: false,
|
||||
isPending: false,
|
||||
isSuccess: true,
|
||||
refetch: vi.fn(),
|
||||
}
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
<AgentConfigureComposerScopeHarness />
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'build mode' }))
|
||||
|
||||
expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent(
|
||||
'readonly:no',
|
||||
)
|
||||
expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent(
|
||||
'publish:yes',
|
||||
)
|
||||
expect(screen.getByRole('region', { name: 'build-chat' })).toHaveTextContent('build:none')
|
||||
expect(screen.queryByRole('status', { name: 'appApi.loading' })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'send build message' })).toBeDisabled()
|
||||
|
||||
refreshBuildConversation.resolve({
|
||||
debug_conversation_has_messages: false,
|
||||
debug_conversation_id: 'debug-conversation-new',
|
||||
debug_conversation_message_count: 0,
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: 'send build message' })).toBeEnabled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should confirm before discarding an existing build draft and switching to preview', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.queryState.composer = {
|
||||
|
||||
@ -310,13 +310,10 @@ function AgentConfigurePageComposerContent({
|
||||
const queryClient = useQueryClient()
|
||||
const showBuildDraftBar = buildDraft.isActive
|
||||
const resetBuildChatState = useCallback(async () => {
|
||||
try {
|
||||
await onRefreshDebugConversationAsync()
|
||||
} finally {
|
||||
setCompletedBuildConversationId(null)
|
||||
setConversationId({ mode: 'build', conversationId: null })
|
||||
setClearChatByMode((current) => ({ ...current, build: true }))
|
||||
}
|
||||
setCompletedBuildConversationId(null)
|
||||
setConversationId({ mode: 'build', conversationId: null })
|
||||
setClearChatByMode((current) => ({ ...current, build: true }))
|
||||
await onRefreshDebugConversationAsync()
|
||||
}, [onRefreshDebugConversationAsync, setClearChatByMode, setConversationId])
|
||||
const rebaseComposerDraftFromSoulConfig = useCallback(
|
||||
(agentSoulConfig?: AgentSoulConfig) => {
|
||||
@ -458,16 +455,11 @@ function AgentConfigurePageComposerContent({
|
||||
textGenerationModelList={textGenerationModelList}
|
||||
draftSavedAt={draftSavedAt}
|
||||
isPublishing={isPublishing}
|
||||
readOnly={
|
||||
isViewingVersion ||
|
||||
buildDraft.isActive ||
|
||||
buildDraftActionsDisabled ||
|
||||
isEnteringBuildMode
|
||||
}
|
||||
readOnly={isViewingVersion || buildDraft.isActive || buildDraftActionsDisabled}
|
||||
selectedVersionSnapshot={isViewingVersion ? activeConfigSnapshot : undefined}
|
||||
isBuildDraftActive={buildDraft.isActive}
|
||||
buildDraftChangedKeys={buildDraft.changedKeys}
|
||||
showPublishBar={!buildDraft.isActive && !isEnteringBuildMode}
|
||||
showPublishBar={!buildDraft.isActive}
|
||||
workflowReferencesEnabled={agentQuery.isSuccess}
|
||||
bottomAction={
|
||||
showBuildDraftBar ? (
|
||||
@ -519,7 +511,7 @@ function AgentConfigurePageComposerContent({
|
||||
/>
|
||||
}
|
||||
chat={
|
||||
buildDraft.isPending || isEnteringBuildMode ? (
|
||||
buildDraft.isPending ? (
|
||||
<Loading type="app" />
|
||||
) : (
|
||||
<AgentConfigureRightPanelChat
|
||||
@ -532,6 +524,7 @@ function AgentConfigurePageComposerContent({
|
||||
clearChatList={clearChatByMode[rightPanelChatMode]}
|
||||
controllerRef={rightPanelChatControllerRef}
|
||||
conversationIds={conversationIds}
|
||||
disabled={isEnteringBuildMode}
|
||||
mode={rightPanelChatMode}
|
||||
speechToTextDraftType={
|
||||
rightPanelChatMode === 'build' && buildDraft.isActive ? 'debug_build' : 'draft'
|
||||
|
||||
@ -30,6 +30,7 @@ export type AgentChatRuntimeProps = {
|
||||
clearChatList: boolean
|
||||
controllerRef?: Ref<AgentPreviewChatController>
|
||||
conversationId?: string | null
|
||||
disabled?: boolean
|
||||
draftType?: 'debug_build'
|
||||
speechToTextDraftType?: 'draft' | 'debug_build'
|
||||
inputPlaceholder: string
|
||||
@ -57,6 +58,7 @@ export function AgentChatRuntime({
|
||||
clearChatList,
|
||||
controllerRef,
|
||||
conversationId,
|
||||
disabled,
|
||||
draftType,
|
||||
speechToTextDraftType,
|
||||
inputPlaceholder,
|
||||
@ -136,6 +138,7 @@ export function AgentChatRuntime({
|
||||
clearChatList={clearChatList}
|
||||
controllerRef={controllerRef}
|
||||
conversationId={conversationId}
|
||||
disabled={disabled}
|
||||
draftType={draftType}
|
||||
speechToTextDraftType={speechToTextDraftType}
|
||||
initialChatTree={initialChatTree}
|
||||
|
||||
@ -34,6 +34,7 @@ export function AgentPreviewChatSession({
|
||||
clearChatList,
|
||||
controllerRef,
|
||||
conversationId,
|
||||
disabled,
|
||||
draftType,
|
||||
speechToTextDraftType,
|
||||
initialChatTree,
|
||||
@ -61,6 +62,7 @@ export function AgentPreviewChatSession({
|
||||
clearChatList: boolean
|
||||
controllerRef?: Ref<AgentPreviewChatController>
|
||||
conversationId?: string | null
|
||||
disabled?: boolean
|
||||
draftType?: 'debug_build'
|
||||
speechToTextDraftType?: 'draft' | 'debug_build'
|
||||
initialChatTree: ChatItemInTree[]
|
||||
@ -115,8 +117,11 @@ export function AgentPreviewChatSession({
|
||||
files?: FileEntity[],
|
||||
isRegenerate: boolean = false,
|
||||
parentAnswer: ChatItem | null = null,
|
||||
) => conversationRef.current?.send(message, files, isRegenerate, parentAnswer),
|
||||
[],
|
||||
) => {
|
||||
if (disabled) return
|
||||
return conversationRef.current?.send(message, files, isRegenerate, parentAnswer)
|
||||
},
|
||||
[disabled],
|
||||
)
|
||||
useImperativeHandle(
|
||||
controllerRef,
|
||||
@ -141,7 +146,7 @@ export function AgentPreviewChatSession({
|
||||
<ChatInputArea
|
||||
botName={agentName || 'Agent'}
|
||||
customPlaceholder={inputPlaceholder}
|
||||
disabled={isEmptyChat && isResponding}
|
||||
disabled={disabled || (isEmptyChat && isResponding)}
|
||||
// Build chat opts out so it does not steal focus from the configure editor.
|
||||
// oxlint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={isEmptyChat ? inputAutoFocus : undefined}
|
||||
|
||||
@ -365,10 +365,10 @@ export function useAgentConfigureBuildDraftActions({
|
||||
|
||||
const startFreshBuildSession = useCallback(async () => {
|
||||
cancelBuildDraftRefresh()
|
||||
setSoulSourceOverride('draft')
|
||||
try {
|
||||
await resetBuildChatSession()
|
||||
forceCheckoutBeforeNextBuildRunRef.current = true
|
||||
setSoulSourceOverride('draft')
|
||||
return true
|
||||
} catch {
|
||||
toast.error(tCommon(($) => $['api.actionFailed']))
|
||||
|
||||
@ -214,21 +214,23 @@ export function useAgentConfigureSessionController({
|
||||
if (pendingBuildModeTransitionRef.current) return
|
||||
|
||||
setIsEnteringBuildMode(true)
|
||||
modeRef.current = 'build'
|
||||
rotateBuildCallbackGeneration(true)
|
||||
const buildSessionStart = startFreshBuildSession()
|
||||
const modeChange = (async () => onModeChange(nextMode))()
|
||||
const transition = (async () => {
|
||||
try {
|
||||
const started = await startFreshBuildSession()
|
||||
if (!started || modeRef.current !== 'preview') return
|
||||
const [buildSessionResult, modeChangeResult] = await Promise.allSettled([
|
||||
buildSessionStart,
|
||||
modeChange,
|
||||
])
|
||||
const buildSessionStarted =
|
||||
buildSessionResult.status === 'fulfilled' && buildSessionResult.value
|
||||
const modeChanged = modeChangeResult.status === 'fulfilled'
|
||||
if ((buildSessionStarted && modeChanged) || modeRef.current !== 'build') return
|
||||
|
||||
modeRef.current = 'build'
|
||||
rotateBuildCallbackGeneration(true)
|
||||
try {
|
||||
await onModeChange(nextMode)
|
||||
} catch (error) {
|
||||
modeRef.current = 'preview'
|
||||
rotateBuildCallbackGeneration(false)
|
||||
throw error
|
||||
}
|
||||
} catch {}
|
||||
modeRef.current = 'preview'
|
||||
rotateBuildCallbackGeneration(false)
|
||||
await Promise.resolve(onModeChange('preview')).catch(() => undefined)
|
||||
})()
|
||||
pendingBuildModeTransitionRef.current = transition
|
||||
void transition.finally(() => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user