From 69e9fa68db478e631e9508be7ac9a1b67b8ff069 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 25 Jun 2026 15:43:44 +0800 Subject: [PATCH] feat: build draft --- .../configure/__tests__/page.spec.tsx | 340 +++++++++++++++++- .../orchestrate/build-draft-bar.tsx | 63 ++++ .../components/orchestrate/index.tsx | 11 +- .../orchestrate/publish-bar/index.tsx | 2 +- .../preview/__tests__/chat.spec.tsx | 17 + .../components/preview/chat-runtime.tsx | 19 +- .../agent-v2/agent-detail/configure/page.tsx | 128 +++++-- .../use-agent-configure-build-draft.ts | 205 +++++++++++ web/i18n/ar-TN/agent-v-2.json | 4 + web/i18n/de-DE/agent-v-2.json | 4 + web/i18n/en-US/agent-v-2.json | 4 + web/i18n/es-ES/agent-v-2.json | 4 + web/i18n/fa-IR/agent-v-2.json | 4 + web/i18n/fr-FR/agent-v-2.json | 4 + web/i18n/hi-IN/agent-v-2.json | 4 + web/i18n/id-ID/agent-v-2.json | 4 + web/i18n/it-IT/agent-v-2.json | 4 + web/i18n/ja-JP/agent-v-2.json | 4 + web/i18n/ko-KR/agent-v-2.json | 4 + web/i18n/nl-NL/agent-v-2.json | 4 + web/i18n/pl-PL/agent-v-2.json | 4 + web/i18n/pt-BR/agent-v-2.json | 4 + web/i18n/ro-RO/agent-v-2.json | 4 + web/i18n/ru-RU/agent-v-2.json | 4 + web/i18n/sl-SI/agent-v-2.json | 4 + web/i18n/th-TH/agent-v-2.json | 4 + web/i18n/tr-TR/agent-v-2.json | 4 + web/i18n/uk-UA/agent-v-2.json | 4 + web/i18n/vi-VN/agent-v-2.json | 4 + web/i18n/zh-Hans/agent-v-2.json | 4 + web/i18n/zh-Hant/agent-v-2.json | 4 + 31 files changed, 845 insertions(+), 32 deletions(-) create mode 100644 web/features/agent-v2/agent-detail/configure/components/orchestrate/build-draft-bar.tsx create mode 100644 web/features/agent-v2/agent-detail/configure/use-agent-configure-build-draft.ts diff --git a/web/features/agent-v2/agent-detail/configure/__tests__/page.spec.tsx b/web/features/agent-v2/agent-detail/configure/__tests__/page.spec.tsx index 84de0d50624..532bce01ce8 100644 --- a/web/features/agent-v2/agent-detail/configure/__tests__/page.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/__tests__/page.spec.tsx @@ -1,9 +1,13 @@ +import type { ReactNode } from 'react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen } from '@testing-library/react' +import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { AgentConfigurePage } from '../page' const mocks = vi.hoisted(() => ({ + applyBuildDraft: vi.fn(), + checkoutBuildDraft: vi.fn(), + discardBuildDraft: vi.fn(), refreshDebugConversation: vi.fn(), queryState: { agent: { @@ -15,21 +19,35 @@ const mocks = vi.hoisted(() => ({ name: 'Research Agent', }, isFetching: false, + isError: false, isPending: false, isSuccess: true, }, composer: { data: undefined as unknown, isFetching: true, + isError: false, isPending: true, isSuccess: false, + refetch: vi.fn(), }, version: { data: undefined as unknown, isFetching: false, + isError: false, isPending: false, isSuccess: false, }, + buildDraft: { + data: undefined as unknown, + dataUpdatedAt: 0, + error: null as unknown, + isFetching: false, + isError: false, + isPending: false, + isSuccess: false, + refetch: vi.fn(), + }, }, })) @@ -47,10 +65,13 @@ vi.mock('@tanstack/react-query', async (importOriginal) => { return mocks.queryState.composer if (queryKey === 'version') return mocks.queryState.version + if (queryKey === 'build-draft') + return mocks.queryState.buildDraft return { data: undefined, isFetching: false, + isError: false, isPending: false, isSuccess: false, } @@ -85,6 +106,24 @@ vi.mock('@/service/client', () => ({ mutationOptions: () => ({ mutationFn: vi.fn() }), }, }, + buildDraft: { + get: { + queryOptions: () => ({ queryKey: ['build-draft'] }), + }, + checkout: { + post: { + mutationOptions: () => ({ mutationFn: mocks.checkoutBuildDraft }), + }, + }, + apply: { + post: { + mutationOptions: () => ({ mutationFn: mocks.applyBuildDraft }), + }, + }, + delete: { + mutationOptions: () => ({ mutationFn: mocks.discardBuildDraft }), + }, + }, versions: { byVersionId: { get: { @@ -108,7 +147,31 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () })) vi.mock('../components/orchestrate', () => ({ - AgentOrchestratePanel: () =>
, + AgentOrchestratePanel: (props: { + bottomBar?: ReactNode + readOnly?: boolean + showPublishBar?: boolean + }) => ( +
+ {`readonly:${props.readOnly ? 'yes' : 'no'}`} + {`publish:${props.showPublishBar ? 'yes' : 'no'}`} + {props.bottomBar} +
+ ), +})) + +vi.mock('../components/orchestrate/build-draft-bar', () => ({ + AgentBuildDraftBar: (props: { + changesCount: number + onApply: () => void + onDiscard: () => void + }) => ( +
+ {`changes:${props.changesCount}`} + + +
+ ), })) vi.mock('../components/preview/build-chat', () => ({ @@ -148,6 +211,7 @@ vi.mock('../components/preview/header', () => ({ mode: 'build' | 'preview' previewEnabled: boolean onModeChange: (mode: 'build' | 'preview') => void + onOpenVersions: () => void onRefresh: () => void }) => (
@@ -158,6 +222,9 @@ vi.mock('../components/preview/header', () => ({ + @@ -177,6 +244,13 @@ describe('AgentConfigurePage', () => { mocks.refreshDebugConversation.mockResolvedValue({ debug_conversation_id: 'debug-conversation-new', }) + mocks.applyBuildDraft.mockResolvedValue({ result: 'success', draft: {} }) + mocks.checkoutBuildDraft.mockResolvedValue({ + variant: 'agent_app', + draft: {}, + agent_soul: {}, + }) + mocks.discardBuildDraft.mockResolvedValue({ result: 'success' }) mocks.queryState.agent = { data: { icon: 'agent', @@ -186,21 +260,35 @@ describe('AgentConfigurePage', () => { debug_conversation_id: 'debug-conversation-old', }, isFetching: false, + isError: false, isPending: false, isSuccess: true, } mocks.queryState.composer = { data: undefined as unknown, isFetching: true, + isError: false, isPending: true, isSuccess: false, + refetch: vi.fn(), } mocks.queryState.version = { data: undefined as unknown, isFetching: false, + isError: false, isPending: false, isSuccess: false, } + mocks.queryState.buildDraft = { + data: undefined as unknown, + dataUpdatedAt: 0, + error: null, + isFetching: false, + isError: false, + isPending: false, + isSuccess: false, + refetch: vi.fn(), + } }) describe('Loading state', () => { @@ -225,8 +313,10 @@ describe('AgentConfigurePage', () => { mocks.queryState.composer = { data: {}, isFetching: false, + isError: false, isPending: false, isSuccess: true, + refetch: vi.fn(), } render( @@ -271,8 +361,10 @@ describe('AgentConfigurePage', () => { mocks.queryState.composer = { data: {}, isFetching: false, + isError: false, isPending: false, isSuccess: true, + refetch: vi.fn(), } render( @@ -289,5 +381,249 @@ describe('AgentConfigurePage', () => { expect(screen.getByRole('region', { name: 'build-chat' })).toHaveTextContent('build:debug-conversation-old') expect(screen.queryByRole('region', { name: 'preview-chat', hidden: true })).not.toBeInTheDocument() }) + + it('should stay in normal draft mode when build draft returns 404 even if a debug conversation exists', () => { + const queryClient = new QueryClient() + mocks.queryState.composer = { + data: { + agent_soul: { + prompt: { + system_prompt: 'draft prompt', + }, + }, + }, + isFetching: false, + isError: false, + isPending: false, + isSuccess: true, + refetch: vi.fn(), + } + mocks.queryState.buildDraft = { + data: undefined as unknown, + dataUpdatedAt: 0, + error: new Response(null, { status: 404 }), + isFetching: false, + isError: true, + isPending: false, + isSuccess: false, + refetch: vi.fn(), + } + + render( + + + , + ) + + expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent('readonly:no') + expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent('publish:yes') + expect(screen.queryByRole('region', { name: 'build-draft-bar' })).not.toBeInTheDocument() + }) + + it('should enter build draft mode when build draft data exists', () => { + const queryClient = new QueryClient() + mocks.queryState.composer = { + data: { + agent_soul: { + prompt: { + system_prompt: 'draft prompt', + }, + }, + }, + isFetching: false, + isError: false, + isPending: false, + isSuccess: true, + refetch: vi.fn(), + } + mocks.queryState.buildDraft = { + data: { + agent_soul: { + prompt: { + system_prompt: 'build prompt', + }, + }, + draft: {}, + variant: 'agent_app', + }, + dataUpdatedAt: 1, + error: null, + isFetching: false, + isError: false, + isPending: false, + isSuccess: true, + refetch: vi.fn(), + } + + render( + + + , + ) + + expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent('readonly:yes') + expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent('publish:no') + expect(screen.getByRole('region', { name: 'build-draft-bar' })).toBeInTheDocument() + }) + + it('should switch soul source to view version when selecting a version from build draft mode', async () => { + const user = userEvent.setup() + const queryClient = new QueryClient() + mocks.queryState.composer = { + data: { + agent_soul: { + prompt: { + system_prompt: 'draft prompt', + }, + }, + }, + isFetching: false, + isError: false, + isPending: false, + isSuccess: true, + refetch: vi.fn(), + } + mocks.queryState.buildDraft = { + data: { + agent_soul: { + prompt: { + system_prompt: 'build prompt', + }, + }, + draft: {}, + variant: 'agent_app', + }, + dataUpdatedAt: 1, + error: null, + isFetching: false, + isError: false, + isPending: false, + isSuccess: true, + refetch: vi.fn(), + } + + render( + + + , + ) + + expect(screen.getByRole('region', { name: 'build-draft-bar' })).toBeInTheDocument() + + await user.click(screen.getByRole('button', { name: 'open versions' })) + await user.click(screen.getByRole('button', { name: 'select version' })) + + expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent('readonly:yes') + expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toHaveTextContent('publish:yes') + expect(screen.queryByRole('region', { name: 'build-draft-bar' })).not.toBeInTheDocument() + }) + + it('should apply the build draft and start a new build session', async () => { + const user = userEvent.setup() + const queryClient = new QueryClient() + const refetchComposer = vi.fn().mockResolvedValue({}) + mocks.queryState.composer = { + data: {}, + isFetching: false, + isError: false, + isPending: false, + isSuccess: true, + refetch: refetchComposer, + } + mocks.queryState.buildDraft = { + data: { + agent_soul: {}, + draft: {}, + variant: 'agent_app', + }, + dataUpdatedAt: 1, + error: null, + isFetching: false, + isError: false, + isPending: false, + isSuccess: true, + refetch: vi.fn(), + } + + render( + + + , + ) + + await user.click(screen.getByRole('button', { name: 'apply build draft' })) + + await waitFor(() => expect(mocks.applyBuildDraft).toHaveBeenCalledWith( + { + params: { + agent_id: 'agent-1', + }, + }, + expect.any(Object), + )) + expect(mocks.refreshDebugConversation).toHaveBeenCalledWith({ + params: { + agent_id: 'agent-1', + }, + body: { + debug_conversation_id: 'debug-conversation-old', + }, + }, expect.any(Object)) + expect(refetchComposer).toHaveBeenCalled() + expect(screen.getByRole('region', { name: 'build-chat' })).toHaveTextContent('build:none') + }) + + it('should discard the build draft and start a new build session', async () => { + const user = userEvent.setup() + const queryClient = new QueryClient() + mocks.queryState.composer = { + data: {}, + isFetching: false, + isError: false, + isPending: false, + isSuccess: true, + refetch: vi.fn(), + } + mocks.queryState.buildDraft = { + data: { + agent_soul: {}, + draft: {}, + variant: 'agent_app', + }, + dataUpdatedAt: 1, + error: null, + isFetching: false, + isError: false, + isPending: false, + isSuccess: true, + refetch: vi.fn(), + } + + render( + + + , + ) + + await user.click(screen.getByRole('button', { name: 'discard build draft' })) + + await waitFor(() => expect(mocks.discardBuildDraft).toHaveBeenCalledWith( + { + params: { + agent_id: 'agent-1', + }, + }, + expect.any(Object), + )) + expect(mocks.refreshDebugConversation).toHaveBeenCalledWith({ + params: { + agent_id: 'agent-1', + }, + body: { + debug_conversation_id: 'debug-conversation-old', + }, + }, expect.any(Object)) + expect(screen.getByRole('region', { name: 'build-chat' })).toHaveTextContent('build:none') + }) }) }) diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/build-draft-bar.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/build-draft-bar.tsx new file mode 100644 index 00000000000..61f7bd8de43 --- /dev/null +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/build-draft-bar.tsx @@ -0,0 +1,63 @@ +'use client' + +import { Button } from '@langgenius/dify-ui/button' +import { useTranslation } from 'react-i18next' +import { PublishBarBottomActions } from './publish-bar' + +type AgentBuildDraftBarProps = { + changesCount: number + isApplying?: boolean + isDiscarding?: boolean + onApply: () => void + onDiscard: () => void +} + +export function AgentBuildDraftBar({ + changesCount, + isApplying = false, + isDiscarding = false, + onApply, + onDiscard, +}: AgentBuildDraftBarProps) { + const { t } = useTranslation('agentV2') + const { t: tCustom } = useTranslation('custom') + const isPending = isApplying || isDiscarding + const metaLabel = changesCount > 0 + ? t('agentDetail.configure.buildDraft.changes', { count: changesCount }) + : t('agentDetail.configure.buildDraft.noChanges') + + return ( + +
+
+

+ {t('agentDetail.configure.buildDraft.title')} +

+

+ {metaLabel} +

+
+ + +
+
+ ) +} diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/index.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/index.tsx index c27aa7ae4fe..625e3517770 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/index.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { AgentConfigSnapshotDetailResponse, AgentConfigSnapshotSummaryResponse } from '@dify/contracts/api/console/agent/types.gen' +import type { ReactNode } from 'react' import type { DefaultModel, Model } from '@/app/components/header/account-setting/model-provider-page/declarations' import { cn } from '@langgenius/dify-ui/cn' import { ScrollArea } from '@langgenius/dify-ui/scroll-area' @@ -36,6 +37,7 @@ type AgentOrchestratePanelProps = { selectedVersionSnapshot?: AgentConfigSnapshotSummaryResponse | null showHeader?: boolean showPublishBar?: boolean + bottomBar?: ReactNode onSelectModel: (model: DefaultModel) => void onPublish: () => void | Promise onExitVersions?: () => void @@ -59,6 +61,7 @@ export function AgentOrchestratePanel({ selectedVersionSnapshot, showHeader = true, showPublishBar = true, + bottomBar, onSelectModel, onPublish, onExitVersions, @@ -67,6 +70,7 @@ export function AgentOrchestratePanel({ const { t } = useTranslation('agentV2') const orchestrateHeadingId = 'agent-configure-orchestrate-heading' const orchestrateLabel = t('agentDetail.configure.orchestrate') + const hasBottomBar = showPublishBar || !!bottomBar const driveApiContext = useMemo(() => appId && nodeId ? { agentId, @@ -92,8 +96,8 @@ export function AgentOrchestratePanel({ labelledBy={showHeader ? orchestrateHeadingId : undefined} slotClassNames={{ viewport: 'overscroll-contain', - content: cn('min-h-full px-4 py-3', showPublishBar && 'pb-20'), - scrollbar: showPublishBar ? 'z-20' : undefined, + content: cn('min-h-full px-4 py-3', hasBottomBar && 'pb-20'), + scrollbar: hasBottomBar ? 'z-20' : undefined, }} > @@ -115,7 +119,8 @@ export function AgentOrchestratePanel({
- {showPublishBar && ( + {bottomBar} + {showPublishBar && !bottomBar && ( { expect(handleSendMock).not.toHaveBeenCalled() }) + it('should send build chat with the debug build draft type', async () => { + renderPreviewChat({ + draftType: 'debug_build', + }) + + fireEvent.click(screen.getByRole('button', { name: 'send' })) + + await waitFor(() => expect(handleSendMock).toHaveBeenCalledTimes(1)) + expect(handleSendMock).toHaveBeenCalledWith( + 'agent/agent-1/chat-messages', + expect.objectContaining({ + draft_type: 'debug_build', + }), + expect.any(Object), + ) + }) + it('should keep preview file upload disabled by default', async () => { renderPreviewChat() diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx index 2ab9f1a0a60..22d7abf6f56 100644 --- a/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx @@ -444,10 +444,12 @@ export type AgentChatRuntimeProps = { agentSoulConfig?: AgentSoulConfig clearChatList: boolean conversationId?: string | null + draftType?: 'debug_build' inputPlaceholder: string sendButtonLabel?: string renderEmptyState: (props: AgentChatRuntimeEmptyStateProps) => ReactNode onClearChatListChange: (clearChatList: boolean) => void + onConversationComplete?: (conversationId: string) => void onConversationIdChange?: (conversationId: string) => void onSaveDraftBeforeRun?: () => Promise } @@ -461,10 +463,12 @@ export function AgentChatRuntime({ agentSoulConfig, clearChatList, conversationId, + draftType, inputPlaceholder, sendButtonLabel, renderEmptyState, onClearChatListChange, + onConversationComplete, onConversationIdChange, onSaveDraftBeforeRun, }: AgentChatRuntimeProps) { @@ -497,11 +501,13 @@ export function AgentChatRuntime({ agentSoulConfig={agentSoulConfig} clearChatList={clearChatList} conversationId={conversationId} + draftType={draftType} initialChatTree={initialChatTree} inputPlaceholder={inputPlaceholder} sendButtonLabel={sendButtonLabel} renderEmptyState={renderEmptyState} onClearChatListChange={onClearChatListChange} + onConversationComplete={onConversationComplete} onConversationIdChange={onConversationIdChange} onSaveDraftBeforeRun={onSaveDraftBeforeRun} /> @@ -517,11 +523,13 @@ function AgentPreviewChatSession({ agentSoulConfig, clearChatList, conversationId, + draftType, initialChatTree, inputPlaceholder, sendButtonLabel, renderEmptyState, onClearChatListChange, + onConversationComplete, onConversationIdChange, onSaveDraftBeforeRun, }: { @@ -533,11 +541,13 @@ function AgentPreviewChatSession({ agentSoulConfig?: AgentSoulConfig clearChatList: boolean conversationId?: string | null + draftType?: 'debug_build' initialChatTree: ChatItemInTree[] inputPlaceholder: string sendButtonLabel?: string renderEmptyState: (props: AgentChatRuntimeEmptyStateProps) => ReactNode onClearChatListChange: (clearChatList: boolean) => void + onConversationComplete?: (conversationId: string) => void onConversationIdChange?: (conversationId: string) => void onSaveDraftBeforeRun?: () => Promise }) { @@ -600,6 +610,8 @@ function AgentPreviewChatSession({ inputs, parent_message_id: (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null, } + if (draftType) + data.draft_type = draftType if (files?.length && supportVision) data.files = files @@ -610,10 +622,13 @@ function AgentPreviewChatSession({ { onGetConversationMessages: conversationId => fetchAgentConversationMessages(agentId, conversationId), onGetSuggestedQuestions: responseItemId => fetchAgentSuggestedQuestions(agentId, responseItemId), - onConversationComplete: onConversationIdChange, + onConversationComplete: (conversationId) => { + onConversationIdChange?.(conversationId) + onConversationComplete?.(conversationId) + }, }, ) - }, [agentId, chatList, config.model.name, config.model.provider, handleSend, inputs, onConversationIdChange, onSaveDraftBeforeRun, textGenerationModelList]) + }, [agentId, chatList, config.model.name, config.model.provider, draftType, handleSend, inputs, onConversationComplete, onConversationIdChange, onSaveDraftBeforeRun, textGenerationModelList]) const doRegenerate = useCallback((chatItem: ChatItem, editedQuestion?: { message: string, files?: FileEntity[] }) => { const question = editedQuestion ? chatItem : chatList.find(item => item.id === chatItem.parentMessageId) diff --git a/web/features/agent-v2/agent-detail/configure/page.tsx b/web/features/agent-v2/agent-detail/configure/page.tsx index 5a6cde741aa..07454bac6c5 100644 --- a/web/features/agent-v2/agent-detail/configure/page.tsx +++ b/web/features/agent-v2/agent-detail/configure/page.tsx @@ -2,13 +2,14 @@ import type { AgentAppDetailWithSite, AgentIconType, AgentSoulConfig } from '@dify/contracts/api/console/agent/types.gen' import { useMutation, useQueryClient } from '@tanstack/react-query' -import { useState } from 'react' +import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider' import { useHydrateAgentSoulConfigDraft } from '@/features/agent-v2/agent-composer/store' import { consoleQuery } from '@/service/client' import { AgentOrchestratePanel } from './components/orchestrate' +import { AgentBuildDraftBar } from './components/orchestrate/build-draft-bar' import { AgentBuildPanelBackground } from './components/preview/build-background' import { AgentBuildChat } from './components/preview/build-chat' import { AgentChatFeaturesPanel } from './components/preview/chat-features-panel' @@ -16,6 +17,7 @@ import { AgentPreviewHeader } from './components/preview/header' import { AgentPreviewChat } from './components/preview/preview-chat' import { AgentPreviewVersionsPanel } from './components/preview/versions-panel' import { useAgentConfigureData, useAgentConfigureModelOptions, useAgentPreviewSoulConfig } from './hooks' +import { useAgentConfigureBuildDraftActions, useAgentConfigureBuildDraftData } from './use-agent-configure-build-draft' import { useAgentConfigureSync } from './use-agent-configure-sync' type AgentConfigurePageProps = { @@ -104,6 +106,13 @@ function AgentConfigurePageLoadedContent({ preview: null, }) const rightPanelChatMode: AgentConfigureRightPanelMode = rightPanelMode === 'preview' ? 'build' : rightPanelMode + const buildDraft = useAgentConfigureBuildDraftData({ + agentId, + activeVersionId, + composerAgentSoulConfig: composerQuery.data?.agent_soul, + isViewingVersion, + normalAgentSoulConfig: agentSoulConfig, + }) const refreshDebugConversationMutation = useMutation(consoleQuery.agent.byAgentId.debugConversation.refresh.post.mutationOptions({ onSuccess: ({ debug_conversation_id }) => { queryClient.setQueryData( @@ -120,20 +129,33 @@ function AgentConfigurePageLoadedContent({ ) }, })) - const refreshDebugConversation = (conversationId: string) => { - const input: DebugConversationRefreshInput = { - params: { - agent_id: agentId, - }, - body: { - debug_conversation_id: conversationId, - }, - } + const { + mutate: refreshDebugConversationRequest, + mutateAsync: refreshDebugConversationRequestAsync, + isPending: isRefreshingDebugConversation, + } = refreshDebugConversationMutation + const refreshDebugConversationInput = useCallback((conversationId: string): DebugConversationRefreshInput => ({ + params: { + agent_id: agentId, + }, + body: { + debug_conversation_id: conversationId, + }, + }), [agentId]) + const refreshDebugConversation = useCallback((conversationId: string) => { + const input = refreshDebugConversationInput(conversationId) - refreshDebugConversationMutation.mutate( - input as unknown as Parameters[0], + refreshDebugConversationRequest( + input as unknown as Parameters[0], ) - } + }, [refreshDebugConversationInput, refreshDebugConversationRequest]) + const refreshDebugConversationAsync = useCallback((conversationId: string) => { + const input = refreshDebugConversationInput(conversationId) + + return refreshDebugConversationRequestAsync( + input as unknown as Parameters[0], + ) + }, [refreshDebugConversationInput, refreshDebugConversationRequestAsync]) const updateConversationId = (mode: AgentConfigureRightPanelMode, conversationId: string) => { setConversationIds(current => ({ ...current, @@ -150,11 +172,19 @@ function AgentConfigurePageLoadedContent({ })) setClearPreviewChat(true) } + const resetBuildChatSession = useCallback(async () => { + await refreshDebugConversationAsync(conversationIds.build ?? '') + setConversationIds(current => ({ + ...current, + build: null, + })) + setClearPreviewChat(true) + }, [conversationIds.build, refreshDebugConversationAsync]) useHydrateAgentSoulConfigDraft({ agentId, - activeVersionId, - config: agentSoulConfig, + activeVersionId: buildDraft.activeVersionId, + config: buildDraft.agentSoulConfig, }) const { currentModel, @@ -170,8 +200,33 @@ function AgentConfigurePageLoadedContent({ agentId, baseConfig: agentSoulConfig, currentModel, - enabled: composerQuery.isSuccess && !selectedVersionId, + enabled: composerQuery.isSuccess && !selectedVersionId && !buildDraft.isActive, }) + const buildDraftActions = useAgentConfigureBuildDraftActions({ + agentId, + isActive: buildDraft.isActive, + refetchBuildDraft: buildDraft.refetch, + refetchComposer: composerQuery.refetch, + resetBuildChatSession, + saveDraft, + setSoulSourceOverride: buildDraft.setSoulSourceOverride, + }) + const selectVersion = useCallback((versionId: string | null) => { + buildDraft.setSoulSourceOverride(versionId ? 'view-version' : null) + onSelectVersion(versionId) + }, [buildDraft, onSelectVersion]) + + if (buildDraft.isPending) { + return ( +
+ +
+ ) + } return (
{ + void buildDraftActions.applyBuildDraft() + }} + onDiscard={() => { + void buildDraftActions.discardBuildDraft() + }} + /> + ) + : undefined} onSelectModel={setConfigureModel} onPublish={publishDraft} onOpenVersions={() => setShowPreviewVersions(true)} - onExitVersions={() => onSelectVersion(null)} + onExitVersions={() => selectVersion(null)} /> {/* Preview area */} @@ -209,7 +280,7 @@ function AgentConfigurePageLoadedContent({ onToggleChatFeatures={() => setShowChatFeatures(open => !open)} onOpenVersions={() => setShowPreviewVersions(true)} onRefresh={restartCurrentChat} - refreshDisabled={refreshDebugConversationMutation.isPending} + refreshDisabled={isRefreshingDebugConversation} />
@@ -219,13 +290,15 @@ function AgentConfigurePageLoadedContent({ agentIconBackground={agentQuery.data?.icon_background} agentIconType={agentIconType} agentName={agentQuery.data?.name} - agentSoulConfig={agentSoulConfig} + agentSoulConfig={buildDraft.agentSoulConfig} clearChatList={clearPreviewChat} conversationIds={conversationIds} + draftType={rightPanelChatMode === 'build' ? 'debug_build' : undefined} mode={rightPanelChatMode} onClearChatListChange={setClearPreviewChat} + onConversationComplete={buildDraftActions.refreshBuildDraftAfterBuildChat} onConversationIdChange={updateConversationId} - onSaveDraftBeforeRun={saveDraft} + onSaveDraftBeforeRun={rightPanelChatMode === 'build' ? buildDraftActions.prepareBuildDraftBeforeRun : saveDraft} />
@@ -234,7 +307,7 @@ function AgentConfigurePageLoadedContent({ setShowPreviewVersions(false)} /> )} @@ -253,12 +326,14 @@ function AgentRightPanelChatWithDraftConfig({ agentSoulConfig, conversationIds, mode, + onConversationComplete, onConversationIdChange, ...props -}: Omit[0], 'agentSoulConfig' | 'conversationId' | 'onConversationIdChange'> & { +}: Omit[0], 'agentSoulConfig' | 'conversationId' | 'onConversationComplete' | 'onConversationIdChange'> & { agentSoulConfig?: AgentSoulConfig conversationIds: AgentConfigureConversationIds mode: AgentConfigureRightPanelMode + onConversationComplete?: (mode: AgentConfigureRightPanelMode) => void onConversationIdChange: (mode: AgentConfigureRightPanelMode, conversationId: string) => void }) { const previewAgentSoulConfig = useAgentPreviewSoulConfig(agentSoulConfig) @@ -266,6 +341,9 @@ function AgentRightPanelChatWithDraftConfig({ const handleConversationIdChange = (newConversationId: string) => { onConversationIdChange(mode, newConversationId) } + const handleConversationComplete = () => { + onConversationComplete?.(mode) + } return mode === 'build' ? ( @@ -273,6 +351,7 @@ function AgentRightPanelChatWithDraftConfig({ {...props} conversationId={conversationId} agentSoulConfig={previewAgentSoulConfig} + onConversationComplete={handleConversationComplete} onConversationIdChange={handleConversationIdChange} /> ) @@ -281,6 +360,7 @@ function AgentRightPanelChatWithDraftConfig({ {...props} conversationId={conversationId} agentSoulConfig={previewAgentSoulConfig} + onConversationComplete={handleConversationComplete} onConversationIdChange={handleConversationIdChange} /> ) diff --git a/web/features/agent-v2/agent-detail/configure/use-agent-configure-build-draft.ts b/web/features/agent-v2/agent-detail/configure/use-agent-configure-build-draft.ts new file mode 100644 index 00000000000..a539325979b --- /dev/null +++ b/web/features/agent-v2/agent-detail/configure/use-agent-configure-build-draft.ts @@ -0,0 +1,205 @@ +'use client' + +import type { AgentSoulConfig } from '@dify/contracts/api/console/agent/types.gen' +import { toast } from '@langgenius/dify-ui/toast' +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { agentSoulConfigToFormState } from '@/features/agent-v2/agent-composer/conversions' +import { consoleQuery } from '@/service/client' + +export type AgentConfigureSoulSource = 'draft' | 'build-draft' | 'view-version' + +const isNotFoundResponse = (error: unknown) => error instanceof Response && error.status === 404 + +export function useAgentConfigureBuildDraftData({ + agentId, + activeVersionId, + composerAgentSoulConfig, + isViewingVersion, + normalAgentSoulConfig, +}: { + agentId: string + activeVersionId: string | null | undefined + composerAgentSoulConfig?: AgentSoulConfig + isViewingVersion: boolean + normalAgentSoulConfig?: AgentSoulConfig +}) { + const [soulSourceOverride, setSoulSourceOverride] = useState(null) + const buildDraftQueryOptions = consoleQuery.agent.byAgentId.buildDraft.get.queryOptions({ + input: { + params: { + agent_id: agentId, + }, + }, + }) + const buildDraftQuery = useQuery({ + ...buildDraftQueryOptions, + enabled: !isViewingVersion && soulSourceOverride !== 'draft' && soulSourceOverride !== 'view-version', + queryFn: async (context) => { + try { + return await buildDraftQueryOptions.queryFn(context) + } + catch (error) { + if (isNotFoundResponse(error)) + setSoulSourceOverride('draft') + throw error + } + }, + retry: false, + }) + const { + data: buildDraftData, + dataUpdatedAt: buildDraftDataUpdatedAt, + error: buildDraftError, + isError: isBuildDraftError, + isPending: isBuildDraftPending, + refetch: refetchBuildDraft, + } = buildDraftQuery + const buildDraftNotFound = isNotFoundResponse(buildDraftError) + const soulSource: AgentConfigureSoulSource = isViewingVersion + ? 'view-version' + : soulSourceOverride ?? (!buildDraftNotFound && !!buildDraftData && !isBuildDraftError ? 'build-draft' : 'draft') + const isBuildDraftActive = soulSource === 'build-draft' + const buildDraftAgentSoulConfig = buildDraftData?.agent_soul as AgentSoulConfig | undefined + const visibleAgentSoulConfig = isBuildDraftActive ? buildDraftAgentSoulConfig : normalAgentSoulConfig + const buildDraftChangesCount = useMemo(() => { + if (!buildDraftAgentSoulConfig || !composerAgentSoulConfig) + return 0 + + const normalDraft = agentSoulConfigToFormState(composerAgentSoulConfig) + const buildDraft = agentSoulConfigToFormState(buildDraftAgentSoulConfig) + + return (Object.keys(buildDraft) as Array) + .filter(key => JSON.stringify(buildDraft[key]) !== JSON.stringify(normalDraft[key])) + .length + }, [buildDraftAgentSoulConfig, composerAgentSoulConfig]) + + return { + activeVersionId: isBuildDraftActive ? `build-draft:${buildDraftDataUpdatedAt}` : activeVersionId, + agentSoulConfig: visibleAgentSoulConfig, + changesCount: buildDraftChangesCount, + isActive: isBuildDraftActive, + isPending: !isViewingVersion && soulSourceOverride !== 'draft' && soulSourceOverride !== 'view-version' && isBuildDraftPending, + refetch: refetchBuildDraft, + setSoulSourceOverride, + soulSource, + } +} + +export function useAgentConfigureBuildDraftActions({ + agentId, + isActive, + refetchBuildDraft, + refetchComposer, + resetBuildChatSession, + saveDraft, + setSoulSourceOverride, +}: { + agentId: string + isActive: boolean + refetchBuildDraft: () => Promise + refetchComposer: () => Promise + resetBuildChatSession: () => Promise + saveDraft: () => Promise + setSoulSourceOverride: (source: AgentConfigureSoulSource | null) => void +}) { + const { t: tCommon } = useTranslation('common') + const queryClient = useQueryClient() + const buildDraftRefreshTimerRef = useRef | null>(null) + const buildDraftQueryOptions = consoleQuery.agent.byAgentId.buildDraft.get.queryOptions({ + input: { + params: { + agent_id: agentId, + }, + }, + }) + const checkoutBuildDraftMutation = useMutation(consoleQuery.agent.byAgentId.buildDraft.checkout.post.mutationOptions()) + const applyBuildDraftMutation = useMutation(consoleQuery.agent.byAgentId.buildDraft.apply.post.mutationOptions()) + const discardBuildDraftMutation = useMutation(consoleQuery.agent.byAgentId.buildDraft.delete.mutationOptions()) + const { mutateAsync: checkoutBuildDraft } = checkoutBuildDraftMutation + const { mutateAsync: applyBuildDraftRequest, isPending: isApplyingBuildDraft } = applyBuildDraftMutation + const { mutateAsync: discardBuildDraftRequest, isPending: isDiscardingBuildDraft } = discardBuildDraftMutation + + const prepareBuildDraftBeforeRun = useCallback(async () => { + if (!isActive) + await saveDraft() + + const buildDraft = await checkoutBuildDraft({ + params: { + agent_id: agentId, + }, + body: { + force: false, + }, + }) + queryClient.setQueryData(buildDraftQueryOptions.queryKey, buildDraft) + setSoulSourceOverride('build-draft') + }, [agentId, buildDraftQueryOptions.queryKey, checkoutBuildDraft, isActive, queryClient, saveDraft, setSoulSourceOverride]) + + const refreshBuildDraftAfterBuildChat = useCallback(() => { + if (buildDraftRefreshTimerRef.current) + clearTimeout(buildDraftRefreshTimerRef.current) + + buildDraftRefreshTimerRef.current = setTimeout(() => { + buildDraftRefreshTimerRef.current = null + void refetchBuildDraft() + }, 1000) + }, [refetchBuildDraft]) + + const exitBuildDraftMode = useCallback(async (shouldRefetchComposer: boolean) => { + setSoulSourceOverride('draft') + await resetBuildChatSession() + queryClient.removeQueries({ + queryKey: buildDraftQueryOptions.queryKey, + }) + if (shouldRefetchComposer) + await refetchComposer() + }, [buildDraftQueryOptions.queryKey, queryClient, refetchComposer, resetBuildChatSession, setSoulSourceOverride]) + + const applyBuildDraft = async () => { + try { + await applyBuildDraftRequest({ + params: { + agent_id: agentId, + }, + }) + await exitBuildDraftMode(true) + toast.success(tCommon('api.actionSuccess')) + } + catch { + toast.error(tCommon('api.actionFailed')) + } + } + + const discardBuildDraft = async () => { + try { + await discardBuildDraftRequest({ + params: { + agent_id: agentId, + }, + }) + await exitBuildDraftMode(false) + toast.success(tCommon('api.actionSuccess')) + } + catch { + toast.error(tCommon('api.actionFailed')) + } + } + + useEffect(() => { + return () => { + if (buildDraftRefreshTimerRef.current) + clearTimeout(buildDraftRefreshTimerRef.current) + } + }, []) + + return { + applyBuildDraft, + discardBuildDraft, + isApplyingBuildDraft, + isDiscardingBuildDraft, + prepareBuildDraftBeforeRun, + refreshBuildDraftAfterBuildChat, + } +} diff --git a/web/i18n/ar-TN/agent-v-2.json b/web/i18n/ar-TN/agent-v-2.json index 4fe3137e2c3..14f075f6f05 100644 --- a/web/i18n/ar-TN/agent-v-2.json +++ b/web/i18n/ar-TN/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "ابنِ وكيلك عبر الدردشة", "agentDetail.configure.build.inputPlaceholder": "صِف ما يجب أن يفعله وكيلك", "agentDetail.configure.build.startBuild": "ابدأ البناء", + "agentDetail.configure.buildDraft.changes": "{{count}} تغييرات للتطبيق", + "agentDetail.configure.buildDraft.discard": "تجاهل", + "agentDetail.configure.buildDraft.noChanges": "لا توجد تغييرات للتطبيق", + "agentDetail.configure.buildDraft.title": "مسودة البناء", "agentDetail.configure.chatFeatures.description": "شكّل تجربة الدردشة للمستخدم النهائي على Web app وأسطح الدردشة.", "agentDetail.configure.chatFeatures.title": "ميزات الدردشة", "agentDetail.configure.files.add": "إضافة ملف", diff --git a/web/i18n/de-DE/agent-v-2.json b/web/i18n/de-DE/agent-v-2.json index 94e2ead19ba..c13af050d4b 100644 --- a/web/i18n/de-DE/agent-v-2.json +++ b/web/i18n/de-DE/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Agent per Chat erstellen", "agentDetail.configure.build.inputPlaceholder": "Beschreiben Sie, was Ihr Agent tun soll", "agentDetail.configure.build.startBuild": "Build starten", + "agentDetail.configure.buildDraft.changes": "{{count}} Änderungen anzuwenden", + "agentDetail.configure.buildDraft.discard": "Verwerfen", + "agentDetail.configure.buildDraft.noChanges": "Keine Änderungen anzuwenden", + "agentDetail.configure.buildDraft.title": "Build-Entwurf", "agentDetail.configure.chatFeatures.description": "Gestalten Sie das Chat-Erlebnis für Endnutzer in Ihrer Webapp und in Chat-Oberflächen.", "agentDetail.configure.chatFeatures.title": "Chat-Funktionen", "agentDetail.configure.files.add": "Datei hinzufügen", diff --git a/web/i18n/en-US/agent-v-2.json b/web/i18n/en-US/agent-v-2.json index 3d9324bab1b..006fc77c0f3 100644 --- a/web/i18n/en-US/agent-v-2.json +++ b/web/i18n/en-US/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Build your agent by chatting", "agentDetail.configure.build.inputPlaceholder": "Describe what your agent should do", "agentDetail.configure.build.startBuild": "Start build", + "agentDetail.configure.buildDraft.changes": "{{count}} changes to apply", + "agentDetail.configure.buildDraft.discard": "Discard", + "agentDetail.configure.buildDraft.noChanges": "No changes to apply", + "agentDetail.configure.buildDraft.title": "Build draft", "agentDetail.configure.chatFeatures.description": "Shape the end-user chat experience on your web app and chat surfaces.", "agentDetail.configure.chatFeatures.title": "Chat Features", "agentDetail.configure.files.add": "Add file", diff --git a/web/i18n/es-ES/agent-v-2.json b/web/i18n/es-ES/agent-v-2.json index 9b6a949b156..9073d6b1870 100644 --- a/web/i18n/es-ES/agent-v-2.json +++ b/web/i18n/es-ES/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Crea tu agente chateando", "agentDetail.configure.build.inputPlaceholder": "Describe qué debe hacer tu agente", "agentDetail.configure.build.startBuild": "Iniciar compilación", + "agentDetail.configure.buildDraft.changes": "{{count}} cambios por aplicar", + "agentDetail.configure.buildDraft.discard": "Descartar", + "agentDetail.configure.buildDraft.noChanges": "No hay cambios por aplicar", + "agentDetail.configure.buildDraft.title": "Borrador de compilación", "agentDetail.configure.chatFeatures.description": "Da forma a la experiencia de chat del usuario final en tu webapp y superficies de chat.", "agentDetail.configure.chatFeatures.title": "Funciones de chat", "agentDetail.configure.files.add": "Agregar archivo", diff --git a/web/i18n/fa-IR/agent-v-2.json b/web/i18n/fa-IR/agent-v-2.json index 28351ea5f36..2db59e0b231 100644 --- a/web/i18n/fa-IR/agent-v-2.json +++ b/web/i18n/fa-IR/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "عامل خود را با چت بسازید", "agentDetail.configure.build.inputPlaceholder": "توضیح دهید عامل شما باید چه کاری انجام دهد", "agentDetail.configure.build.startBuild": "شروع ساخت", + "agentDetail.configure.buildDraft.changes": "{{count}} تغییر برای اعمال", + "agentDetail.configure.buildDraft.discard": "رد کردن", + "agentDetail.configure.buildDraft.noChanges": "تغییری برای اعمال وجود ندارد", + "agentDetail.configure.buildDraft.title": "پیش نویس ساخت", "agentDetail.configure.chatFeatures.description": "تجربه چت کاربر نهایی را در Web app و سطوح چت خود شکل دهید.", "agentDetail.configure.chatFeatures.title": "ویژگی‌های چت", "agentDetail.configure.files.add": "افزودن فایل", diff --git a/web/i18n/fr-FR/agent-v-2.json b/web/i18n/fr-FR/agent-v-2.json index a199e39bda5..4ccdab728d4 100644 --- a/web/i18n/fr-FR/agent-v-2.json +++ b/web/i18n/fr-FR/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Créez votre agent par chat", "agentDetail.configure.build.inputPlaceholder": "Décrivez ce que votre agent doit faire", "agentDetail.configure.build.startBuild": "Démarrer la création", + "agentDetail.configure.buildDraft.changes": "{{count}} modifications à appliquer", + "agentDetail.configure.buildDraft.discard": "Ignorer", + "agentDetail.configure.buildDraft.noChanges": "Aucune modification à appliquer", + "agentDetail.configure.buildDraft.title": "Brouillon de build", "agentDetail.configure.chatFeatures.description": "Façonnez l’expérience de chat de l’utilisateur final sur votre webapp et vos surfaces de chat.", "agentDetail.configure.chatFeatures.title": "Fonctionnalités de chat", "agentDetail.configure.files.add": "Ajouter un fichier", diff --git a/web/i18n/hi-IN/agent-v-2.json b/web/i18n/hi-IN/agent-v-2.json index 93705f255d9..55ad92a682f 100644 --- a/web/i18n/hi-IN/agent-v-2.json +++ b/web/i18n/hi-IN/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "चैट करके अपना एजेंट बनाएं", "agentDetail.configure.build.inputPlaceholder": "बताएं कि आपका एजेंट क्या करे", "agentDetail.configure.build.startBuild": "बिल्ड शुरू करें", + "agentDetail.configure.buildDraft.changes": "{{count}} बदलाव लागू करने हैं", + "agentDetail.configure.buildDraft.discard": "छोड़ें", + "agentDetail.configure.buildDraft.noChanges": "लागू करने के लिए कोई बदलाव नहीं", + "agentDetail.configure.buildDraft.title": "बिल्ड ड्राफ्ट", "agentDetail.configure.chatFeatures.description": "अपने Web app और चैट सतहों पर अंतिम-उपयोगकर्ता चैट अनुभव को आकार दें।", "agentDetail.configure.chatFeatures.title": "चैट सुविधाएँ", "agentDetail.configure.files.add": "फ़ाइल जोड़ें", diff --git a/web/i18n/id-ID/agent-v-2.json b/web/i18n/id-ID/agent-v-2.json index 1b5c2c31d46..d9f168cddf0 100644 --- a/web/i18n/id-ID/agent-v-2.json +++ b/web/i18n/id-ID/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Bangun agen Anda lewat chat", "agentDetail.configure.build.inputPlaceholder": "Jelaskan apa yang harus dilakukan agen Anda", "agentDetail.configure.build.startBuild": "Mulai build", + "agentDetail.configure.buildDraft.changes": "{{count}} perubahan untuk diterapkan", + "agentDetail.configure.buildDraft.discard": "Buang", + "agentDetail.configure.buildDraft.noChanges": "Tidak ada perubahan untuk diterapkan", + "agentDetail.configure.buildDraft.title": "Draf build", "agentDetail.configure.chatFeatures.description": "Bentuk pengalaman chat pengguna akhir di Web app dan permukaan chat Anda.", "agentDetail.configure.chatFeatures.title": "Fitur Chat", "agentDetail.configure.files.add": "Tambahkan file", diff --git a/web/i18n/it-IT/agent-v-2.json b/web/i18n/it-IT/agent-v-2.json index 4ce5dac50aa..88638a24c8b 100644 --- a/web/i18n/it-IT/agent-v-2.json +++ b/web/i18n/it-IT/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Crea il tuo agente con la chat", "agentDetail.configure.build.inputPlaceholder": "Descrivi cosa dovrebbe fare il tuo agente", "agentDetail.configure.build.startBuild": "Avvia build", + "agentDetail.configure.buildDraft.changes": "{{count}} modifiche da applicare", + "agentDetail.configure.buildDraft.discard": "Scarta", + "agentDetail.configure.buildDraft.noChanges": "Nessuna modifica da applicare", + "agentDetail.configure.buildDraft.title": "Bozza di build", "agentDetail.configure.chatFeatures.description": "Definisci l’esperienza di chat per l’utente finale sulla tua webapp e sulle superfici di chat.", "agentDetail.configure.chatFeatures.title": "Funzionalità chat", "agentDetail.configure.files.add": "Aggiungi file", diff --git a/web/i18n/ja-JP/agent-v-2.json b/web/i18n/ja-JP/agent-v-2.json index d5f4dc2c559..f1373635ac2 100644 --- a/web/i18n/ja-JP/agent-v-2.json +++ b/web/i18n/ja-JP/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "チャットでエージェントを作成", "agentDetail.configure.build.inputPlaceholder": "エージェントに実行させたいことを説明", "agentDetail.configure.build.startBuild": "ビルドを開始", + "agentDetail.configure.buildDraft.changes": "適用する変更 {{count}} 件", + "agentDetail.configure.buildDraft.discard": "破棄", + "agentDetail.configure.buildDraft.noChanges": "適用する変更はありません", + "agentDetail.configure.buildDraft.title": "ビルドドラフト", "agentDetail.configure.chatFeatures.description": "Web app やチャット画面でのエンドユーザー向けチャット体験を設定します。", "agentDetail.configure.chatFeatures.title": "チャット機能", "agentDetail.configure.files.add": "ファイルを追加", diff --git a/web/i18n/ko-KR/agent-v-2.json b/web/i18n/ko-KR/agent-v-2.json index 41a62e22a63..fc52c7cdfb5 100644 --- a/web/i18n/ko-KR/agent-v-2.json +++ b/web/i18n/ko-KR/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "채팅으로 에이전트 만들기", "agentDetail.configure.build.inputPlaceholder": "에이전트가 해야 할 일을 설명하세요", "agentDetail.configure.build.startBuild": "빌드 시작", + "agentDetail.configure.buildDraft.changes": "적용할 변경 사항 {{count}}개", + "agentDetail.configure.buildDraft.discard": "폐기", + "agentDetail.configure.buildDraft.noChanges": "적용할 변경 사항 없음", + "agentDetail.configure.buildDraft.title": "빌드 초안", "agentDetail.configure.chatFeatures.description": "Web app 및 채팅 화면에서의 최종 사용자 채팅 경험을 구성합니다.", "agentDetail.configure.chatFeatures.title": "채팅 기능", "agentDetail.configure.files.add": "파일 추가", diff --git a/web/i18n/nl-NL/agent-v-2.json b/web/i18n/nl-NL/agent-v-2.json index f21b24446d0..f2bfd786c30 100644 --- a/web/i18n/nl-NL/agent-v-2.json +++ b/web/i18n/nl-NL/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Bouw je agent via chat", "agentDetail.configure.build.inputPlaceholder": "Beschrijf wat je agent moet doen", "agentDetail.configure.build.startBuild": "Build starten", + "agentDetail.configure.buildDraft.changes": "{{count}} wijzigingen om toe te passen", + "agentDetail.configure.buildDraft.discard": "Negeren", + "agentDetail.configure.buildDraft.noChanges": "Geen wijzigingen om toe te passen", + "agentDetail.configure.buildDraft.title": "Buildconcept", "agentDetail.configure.chatFeatures.description": "Geef vorm aan de chatervaring voor eindgebruikers in je webapp en chatoppervlakken.", "agentDetail.configure.chatFeatures.title": "Chatfuncties", "agentDetail.configure.files.add": "Bestand toevoegen", diff --git a/web/i18n/pl-PL/agent-v-2.json b/web/i18n/pl-PL/agent-v-2.json index e8988c6131e..f90a416c9c1 100644 --- a/web/i18n/pl-PL/agent-v-2.json +++ b/web/i18n/pl-PL/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Buduj agenta przez czat", "agentDetail.configure.build.inputPlaceholder": "Opisz, co agent ma robić", "agentDetail.configure.build.startBuild": "Rozpocznij budowanie", + "agentDetail.configure.buildDraft.changes": "{{count}} zmian do zastosowania", + "agentDetail.configure.buildDraft.discard": "Odrzuć", + "agentDetail.configure.buildDraft.noChanges": "Brak zmian do zastosowania", + "agentDetail.configure.buildDraft.title": "Szkic budowania", "agentDetail.configure.chatFeatures.description": "Ukształtuj doświadczenie czatu użytkownika końcowego w aplikacji webowej i powierzchniach czatu.", "agentDetail.configure.chatFeatures.title": "Funkcje czatu", "agentDetail.configure.files.add": "Dodaj plik", diff --git a/web/i18n/pt-BR/agent-v-2.json b/web/i18n/pt-BR/agent-v-2.json index c89f7b6b5da..45a152fff00 100644 --- a/web/i18n/pt-BR/agent-v-2.json +++ b/web/i18n/pt-BR/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Crie seu agente conversando", "agentDetail.configure.build.inputPlaceholder": "Descreva o que seu agente deve fazer", "agentDetail.configure.build.startBuild": "Iniciar build", + "agentDetail.configure.buildDraft.changes": "{{count}} alterações para aplicar", + "agentDetail.configure.buildDraft.discard": "Descartar", + "agentDetail.configure.buildDraft.noChanges": "Nenhuma alteração para aplicar", + "agentDetail.configure.buildDraft.title": "Rascunho de build", "agentDetail.configure.chatFeatures.description": "Modele a experiência de chat do usuário final no seu webapp e superfícies de chat.", "agentDetail.configure.chatFeatures.title": "Recursos de chat", "agentDetail.configure.files.add": "Adicionar arquivo", diff --git a/web/i18n/ro-RO/agent-v-2.json b/web/i18n/ro-RO/agent-v-2.json index 363f44fa70c..f4bbe731a59 100644 --- a/web/i18n/ro-RO/agent-v-2.json +++ b/web/i18n/ro-RO/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Construiește agentul prin chat", "agentDetail.configure.build.inputPlaceholder": "Descrie ce ar trebui să facă agentul tău", "agentDetail.configure.build.startBuild": "Pornește build-ul", + "agentDetail.configure.buildDraft.changes": "{{count}} modificări de aplicat", + "agentDetail.configure.buildDraft.discard": "Renunță", + "agentDetail.configure.buildDraft.noChanges": "Nu există modificări de aplicat", + "agentDetail.configure.buildDraft.title": "Schiță de build", "agentDetail.configure.chatFeatures.description": "Modelează experiența de chat a utilizatorului final pe webapp-ul tău și pe suprafețele de chat.", "agentDetail.configure.chatFeatures.title": "Funcții de chat", "agentDetail.configure.files.add": "Adaugă fișier", diff --git a/web/i18n/ru-RU/agent-v-2.json b/web/i18n/ru-RU/agent-v-2.json index 044dce16a13..a9e75680b2d 100644 --- a/web/i18n/ru-RU/agent-v-2.json +++ b/web/i18n/ru-RU/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Создайте агента в чате", "agentDetail.configure.build.inputPlaceholder": "Опишите, что должен делать агент", "agentDetail.configure.build.startBuild": "Начать сборку", + "agentDetail.configure.buildDraft.changes": "{{count}} изменений для применения", + "agentDetail.configure.buildDraft.discard": "Отменить", + "agentDetail.configure.buildDraft.noChanges": "Нет изменений для применения", + "agentDetail.configure.buildDraft.title": "Черновик сборки", "agentDetail.configure.chatFeatures.description": "Настройте чат-опыт конечного пользователя в вашем веб-приложении и чат-поверхностях.", "agentDetail.configure.chatFeatures.title": "Функции чата", "agentDetail.configure.files.add": "Добавить файл", diff --git a/web/i18n/sl-SI/agent-v-2.json b/web/i18n/sl-SI/agent-v-2.json index 915fc201304..a36928cd8fe 100644 --- a/web/i18n/sl-SI/agent-v-2.json +++ b/web/i18n/sl-SI/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Izdelajte agenta s klepetom", "agentDetail.configure.build.inputPlaceholder": "Opišite, kaj naj vaš agent počne", "agentDetail.configure.build.startBuild": "Začni gradnjo", + "agentDetail.configure.buildDraft.changes": "{{count}} sprememb za uporabo", + "agentDetail.configure.buildDraft.discard": "Zavrzi", + "agentDetail.configure.buildDraft.noChanges": "Ni sprememb za uporabo", + "agentDetail.configure.buildDraft.title": "Osnutek gradnje", "agentDetail.configure.chatFeatures.description": "Oblikujte uporabniško izkušnjo klepeta v vaši spletni aplikaciji in klepetalnih površinah.", "agentDetail.configure.chatFeatures.title": "Funkcije klepeta", "agentDetail.configure.files.add": "Dodaj datoteko", diff --git a/web/i18n/th-TH/agent-v-2.json b/web/i18n/th-TH/agent-v-2.json index 900a448908a..86beff4eed1 100644 --- a/web/i18n/th-TH/agent-v-2.json +++ b/web/i18n/th-TH/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "สร้างเอเจนต์ของคุณด้วยการแชท", "agentDetail.configure.build.inputPlaceholder": "อธิบายว่าเอเจนต์ของคุณควรทำอะไร", "agentDetail.configure.build.startBuild": "เริ่มสร้าง", + "agentDetail.configure.buildDraft.changes": "{{count}} รายการเปลี่ยนแปลงที่ต้องใช้", + "agentDetail.configure.buildDraft.discard": "ทิ้ง", + "agentDetail.configure.buildDraft.noChanges": "ไม่มีการเปลี่ยนแปลงที่ต้องใช้", + "agentDetail.configure.buildDraft.title": "ฉบับร่าง Build", "agentDetail.configure.chatFeatures.description": "กำหนดประสบการณ์การแชทของผู้ใช้ปลายทางบน Web app และหน้าจอแชท", "agentDetail.configure.chatFeatures.title": "ฟีเจอร์แชท", "agentDetail.configure.files.add": "เพิ่มไฟล์", diff --git a/web/i18n/tr-TR/agent-v-2.json b/web/i18n/tr-TR/agent-v-2.json index a3c875364c7..9c87f80ea49 100644 --- a/web/i18n/tr-TR/agent-v-2.json +++ b/web/i18n/tr-TR/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Aracınızı sohbet ederek oluşturun", "agentDetail.configure.build.inputPlaceholder": "Aracınızın ne yapması gerektiğini açıklayın", "agentDetail.configure.build.startBuild": "Build’i başlat", + "agentDetail.configure.buildDraft.changes": "Uygulanacak {{count}} değişiklik", + "agentDetail.configure.buildDraft.discard": "Vazgeç", + "agentDetail.configure.buildDraft.noChanges": "Uygulanacak değişiklik yok", + "agentDetail.configure.buildDraft.title": "Build taslağı", "agentDetail.configure.chatFeatures.description": "Web app ve sohbet yüzeylerinizde son kullanıcı sohbet deneyimini şekillendirin.", "agentDetail.configure.chatFeatures.title": "Sohbet Özellikleri", "agentDetail.configure.files.add": "Dosya ekle", diff --git a/web/i18n/uk-UA/agent-v-2.json b/web/i18n/uk-UA/agent-v-2.json index b11e0678652..96a29504867 100644 --- a/web/i18n/uk-UA/agent-v-2.json +++ b/web/i18n/uk-UA/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Створіть агента через чат", "agentDetail.configure.build.inputPlaceholder": "Опишіть, що має робити ваш агент", "agentDetail.configure.build.startBuild": "Почати збірку", + "agentDetail.configure.buildDraft.changes": "{{count}} змін для застосування", + "agentDetail.configure.buildDraft.discard": "Відхилити", + "agentDetail.configure.buildDraft.noChanges": "Немає змін для застосування", + "agentDetail.configure.buildDraft.title": "Чернетка збірки", "agentDetail.configure.chatFeatures.description": "Налаштуйте чат-досвід кінцевого користувача у вашому веб-застосунку та чат-поверхнях.", "agentDetail.configure.chatFeatures.title": "Функції чату", "agentDetail.configure.files.add": "Додати файл", diff --git a/web/i18n/vi-VN/agent-v-2.json b/web/i18n/vi-VN/agent-v-2.json index cc26737b237..6cfd4f05875 100644 --- a/web/i18n/vi-VN/agent-v-2.json +++ b/web/i18n/vi-VN/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "Xây dựng tác nhân bằng trò chuyện", "agentDetail.configure.build.inputPlaceholder": "Mô tả tác nhân của bạn nên làm gì", "agentDetail.configure.build.startBuild": "Bắt đầu build", + "agentDetail.configure.buildDraft.changes": "{{count}} thay đổi cần áp dụng", + "agentDetail.configure.buildDraft.discard": "Hủy bỏ", + "agentDetail.configure.buildDraft.noChanges": "Không có thay đổi cần áp dụng", + "agentDetail.configure.buildDraft.title": "Bản nháp build", "agentDetail.configure.chatFeatures.description": "Định hình trải nghiệm trò chuyện cho người dùng cuối trên Web app và các bề mặt trò chuyện.", "agentDetail.configure.chatFeatures.title": "Tính năng trò chuyện", "agentDetail.configure.files.add": "Thêm tệp", diff --git a/web/i18n/zh-Hans/agent-v-2.json b/web/i18n/zh-Hans/agent-v-2.json index f07852f30ea..caf778f6dd7 100644 --- a/web/i18n/zh-Hans/agent-v-2.json +++ b/web/i18n/zh-Hans/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "通过对话构建 Agent", "agentDetail.configure.build.inputPlaceholder": "描述你的 Agent 应该做什么", "agentDetail.configure.build.startBuild": "开始构建", + "agentDetail.configure.buildDraft.changes": "{{count}} 项待应用更改", + "agentDetail.configure.buildDraft.discard": "放弃", + "agentDetail.configure.buildDraft.noChanges": "没有待应用的更改", + "agentDetail.configure.buildDraft.title": "Build 草稿", "agentDetail.configure.chatFeatures.description": "配置 Web app 和聊天界面的终端用户聊天体验。", "agentDetail.configure.chatFeatures.title": "Chat 功能", "agentDetail.configure.files.add": "添加文件", diff --git a/web/i18n/zh-Hant/agent-v-2.json b/web/i18n/zh-Hant/agent-v-2.json index a662e1ab2f1..07b67f8b338 100644 --- a/web/i18n/zh-Hant/agent-v-2.json +++ b/web/i18n/zh-Hant/agent-v-2.json @@ -64,6 +64,10 @@ "agentDetail.configure.build.empty.title": "透過對話建置 Agent", "agentDetail.configure.build.inputPlaceholder": "描述你的 Agent 應該做什麼", "agentDetail.configure.build.startBuild": "開始構建", + "agentDetail.configure.buildDraft.changes": "{{count}} 項待套用變更", + "agentDetail.configure.buildDraft.discard": "放棄", + "agentDetail.configure.buildDraft.noChanges": "沒有待套用的變更", + "agentDetail.configure.buildDraft.title": "Build 草稿", "agentDetail.configure.chatFeatures.description": "配置 Web app 和聊天介面的終端使用者聊天體驗。", "agentDetail.configure.chatFeatures.title": "Chat 功能", "agentDetail.configure.files.add": "新增檔案",