feat: build draft

This commit is contained in:
Joel 2026-06-25 15:43:44 +08:00
parent d070074164
commit 69e9fa68db
31 changed files with 845 additions and 32 deletions

View File

@ -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: () => <div role="region" aria-label="orchestrate-panel" />,
AgentOrchestratePanel: (props: {
bottomBar?: ReactNode
readOnly?: boolean
showPublishBar?: boolean
}) => (
<div role="region" aria-label="orchestrate-panel">
<span>{`readonly:${props.readOnly ? 'yes' : 'no'}`}</span>
<span>{`publish:${props.showPublishBar ? 'yes' : 'no'}`}</span>
{props.bottomBar}
</div>
),
}))
vi.mock('../components/orchestrate/build-draft-bar', () => ({
AgentBuildDraftBar: (props: {
changesCount: number
onApply: () => void
onDiscard: () => void
}) => (
<div role="region" aria-label="build-draft-bar">
<span>{`changes:${props.changesCount}`}</span>
<button type="button" onClick={props.onApply}>apply build draft</button>
<button type="button" onClick={props.onDiscard}>discard build draft</button>
</div>
),
}))
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
}) => (
<div>
@ -158,6 +222,9 @@ vi.mock('../components/preview/header', () => ({
<button type="button" onClick={() => props.onModeChange('build')}>
build mode
</button>
<button type="button" onClick={props.onOpenVersions}>
open versions
</button>
<button type="button" onClick={props.onRefresh}>
restart preview
</button>
@ -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(
<QueryClientProvider client={queryClient}>
<AgentConfigurePage agentId="agent-1" />
</QueryClientProvider>,
)
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(
<QueryClientProvider client={queryClient}>
<AgentConfigurePage agentId="agent-1" />
</QueryClientProvider>,
)
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(
<QueryClientProvider client={queryClient}>
<AgentConfigurePage agentId="agent-1" />
</QueryClientProvider>,
)
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(
<QueryClientProvider client={queryClient}>
<AgentConfigurePage agentId="agent-1" />
</QueryClientProvider>,
)
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(
<QueryClientProvider client={queryClient}>
<AgentConfigurePage agentId="agent-1" />
</QueryClientProvider>,
)
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')
})
})
})

View File

@ -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 (
<PublishBarBottomActions>
<div className="pointer-events-auto flex max-w-full min-w-0 items-center gap-2 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur py-2 pr-2 pl-4 shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]">
<div className="flex min-w-0 flex-1 flex-col justify-center gap-0.5 pr-2">
<p className="min-w-0 truncate system-sm-semibold text-text-primary">
{t('agentDetail.configure.buildDraft.title')}
</p>
<p className="min-w-0 truncate system-xs-regular text-text-tertiary">
{metaLabel}
</p>
</div>
<Button
type="button"
variant="primary"
loading={isApplying}
disabled={isPending}
className="h-8 rounded-lg px-3"
onClick={onApply}
>
{tCustom('apply')}
</Button>
<Button
type="button"
variant="secondary"
loading={isDiscarding}
disabled={isPending}
className="h-8 rounded-lg px-3"
onClick={onDiscard}
>
{t('agentDetail.configure.buildDraft.discard')}
</Button>
</div>
</PublishBarBottomActions>
)
}

View File

@ -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<void>
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,
}}
>
<AgentDriveApiContextProvider value={driveApiContext}>
@ -115,7 +119,8 @@ export function AgentOrchestratePanel({
</div>
</AgentOrchestrateReadOnlyContext>
{showPublishBar && (
{bottomBar}
{showPublishBar && !bottomBar && (
<AgentConfigurePublishBar
agentId={agentId}
activeConfigIsPublished={activeConfigIsPublished}

View File

@ -303,7 +303,7 @@ export function AgentConfigurePublishBar({
)
}
function PublishBarBottomActions({
export function PublishBarBottomActions({
children,
}: {
children: ReactNode

View File

@ -270,6 +270,23 @@ describe('AgentPreviewChat', () => {
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()

View File

@ -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<void>
}
@ -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<void>
}) {
@ -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)

View File

@ -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<AgentAppDetailWithSite | undefined>(
@ -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<typeof refreshDebugConversationMutation.mutate>[0],
refreshDebugConversationRequest(
input as unknown as Parameters<typeof refreshDebugConversationRequest>[0],
)
}
}, [refreshDebugConversationInput, refreshDebugConversationRequest])
const refreshDebugConversationAsync = useCallback((conversationId: string) => {
const input = refreshDebugConversationInput(conversationId)
return refreshDebugConversationRequestAsync(
input as unknown as Parameters<typeof refreshDebugConversationRequestAsync>[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 (
<section
aria-label={t('agentDetail.sections.configure')}
aria-busy
className="flex h-full min-w-0 flex-1 items-center justify-center p-1"
>
<Loading type="app" />
</section>
)
}
return (
<section
@ -183,18 +238,34 @@ function AgentConfigurePageLoadedContent({
agentId={agentId}
activeConfigIsPublished={agentQuery.data?.active_config_is_published}
activeConfigSnapshot={activeConfigSnapshot}
agentSoulConfig={agentSoulConfig}
agentSoulConfig={buildDraft.agentSoulConfig}
agentName={agentQuery.data?.name}
currentModel={currentModel}
textGenerationModelList={textGenerationModelList}
draftSavedAt={draftSavedAt}
isPublishing={isPublishing}
readOnly={isViewingVersion}
readOnly={isViewingVersion || buildDraft.isActive}
selectedVersionSnapshot={isViewingVersion ? activeConfigSnapshot : undefined}
showPublishBar={!buildDraft.isActive}
bottomBar={buildDraft.isActive
? (
<AgentBuildDraftBar
changesCount={buildDraft.changesCount}
isApplying={buildDraftActions.isApplyingBuildDraft}
isDiscarding={buildDraftActions.isDiscardingBuildDraft}
onApply={() => {
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}
/>
<div className="relative z-1 min-h-0 flex-1">
@ -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}
/>
</div>
</div>
@ -234,7 +307,7 @@ function AgentConfigurePageLoadedContent({
<AgentPreviewVersionsPanel
agentId={agentId}
activeVersionId={activeVersionId}
onSelectVersion={onSelectVersion}
onSelectVersion={selectVersion}
onClose={() => setShowPreviewVersions(false)}
/>
)}
@ -253,12 +326,14 @@ function AgentRightPanelChatWithDraftConfig({
agentSoulConfig,
conversationIds,
mode,
onConversationComplete,
onConversationIdChange,
...props
}: Omit<Parameters<typeof AgentPreviewChat>[0], 'agentSoulConfig' | 'conversationId' | 'onConversationIdChange'> & {
}: Omit<Parameters<typeof AgentPreviewChat>[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}
/>
)

View File

@ -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<AgentConfigureSoulSource | null>(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<keyof typeof buildDraft>)
.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<unknown>
refetchComposer: () => Promise<unknown>
resetBuildChatSession: () => Promise<void>
saveDraft: () => Promise<void>
setSoulSourceOverride: (source: AgentConfigureSoulSource | null) => void
}) {
const { t: tCommon } = useTranslation('common')
const queryClient = useQueryClient()
const buildDraftRefreshTimerRef = useRef<ReturnType<typeof setTimeout> | 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,
}
}

View File

@ -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": "إضافة ملف",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "افزودن فایل",

View File

@ -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 lexpérience de chat de lutilisateur final sur votre webapp et vos surfaces de chat.",
"agentDetail.configure.chatFeatures.title": "Fonctionnalités de chat",
"agentDetail.configure.files.add": "Ajouter un fichier",

View File

@ -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": "फ़ाइल जोड़ें",

View File

@ -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",

View File

@ -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 lesperienza di chat per lutente finale sulla tua webapp e sulle superfici di chat.",
"agentDetail.configure.chatFeatures.title": "Funzionalità chat",
"agentDetail.configure.files.add": "Aggiungi file",

View File

@ -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": "ファイルを追加",

View File

@ -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": "파일 추가",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "Добавить файл",

View File

@ -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",

View File

@ -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": "เพิ่มไฟล์",

View File

@ -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": "Buildi 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",

View File

@ -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": "Додати файл",

View File

@ -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",

View File

@ -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": "添加文件",

View File

@ -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": "新增檔案",