feat(agent): add publish success guidance (#42645)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jingyi <jingyi.qi@dify.ai>
This commit is contained in:
authored and GitHub committed 2026-09-22 08:17:07 +00:00
1 parent 0cc53efe06
commit 54cc748a36
46 files changed
+926 -111

No files matched your search

+7
View File
@@ -292,6 +292,13 @@ class AgentPublishPayload(BaseModel):
class AgentPublishResponse(ResponseModel):
result: str
publication_kind: Literal["first", "update"] = Field(
description=(
"Classifies the publication by whether the Agent had a publish-visible active snapshot before publishing: "
"'first' if none existed, otherwise 'update'. This is not a historical first-publish indicator. "
"It does not depend on draft edits, Web App or API enablement, or the requesting user's access permissions."
)
)
active_config_snapshot_id: str
active_config_snapshot: AgentConfigSnapshotSummaryResponse | None = None
draft: AgentConfigDraftSummaryResponse | None = None
+1
View File
@@ -14908,6 +14908,7 @@ section may be empty, which is how callers express "no knowledge layer".
| active_config_snapshot | [AgentConfigSnapshotSummaryResponse](#agentconfigsnapshotsummaryresponse) | | No |
| active_config_snapshot_id | string | | Yes |
| draft | [AgentConfigDraftSummaryResponse](#agentconfigdraftsummaryresponse) | | No |
| publication_kind | string, <br>**Available values:** "first", "update" | Classifies the publication by whether the Agent had a publish-visible active snapshot before publishing: 'first' if none existed, otherwise 'update'. This is not a historical first-publish indicator. It does not depend on draft edits, Web App or API enablement, or the requesting user's access permissions.<br>*Enum:* `"first"`, `"update"` | Yes |
| result | string | | Yes |
#### AgentPublishedReferenceResponse
+1
View File
@@ -707,6 +707,7 @@ class AgentComposerService:
)
return {
"result": "success",
"publication_kind": "update" if access_was_ready else "first",
"active_config_snapshot_id": version.id,
"active_config_snapshot": cls._serialize_version(version),
"draft": cls._serialize_draft(draft),
@@ -1143,6 +1143,8 @@ def test_repeated_publish_reuses_normal_draft_home_without_creating_resources(
account_id="account-1",
)
assert first["publication_kind"] == "first"
assert second["publication_kind"] == "update"
assert first["active_config_snapshot_id"] == "version-2"
assert second["active_config_snapshot_id"] == "version-3"
assert published_homes == ["home-1", "home-1"]
@@ -366,6 +366,7 @@ export type AgentPublishResponse = {
active_config_snapshot?: AgentConfigSnapshotSummaryResponse | null
active_config_snapshot_id: string
draft?: AgentConfigDraftSummaryResponse | null
publication_kind: 'first' | 'update'
result: string
}
@@ -1102,6 +1102,7 @@ export const zAgentPublishResponse = z.object({
active_config_snapshot: zAgentConfigSnapshotSummaryResponse.nullish(),
active_config_snapshot_id: z.string(),
draft: zAgentConfigDraftSummaryResponse.nullish(),
publication_kind: z.enum(['first', 'update']),
result: z.string(),
})
@@ -168,7 +168,6 @@ function WorkflowRosterAgentOrchestratePanelContentInner({
showPublishBar={false}
className="h-full max-w-none min-w-0 flex-none rounded-none border-0"
onSelectModel={setConfigureModel}
onPublish={() => undefined}
onOpenVersions={() => undefined}
/>
)
@@ -709,9 +708,6 @@ function WorkflowInlineAgentConfigureWorkspaceContent({
}
className="min-w-90"
onSelectModel={setConfigureModel}
onPublish={() => {
void saveDraft()
}}
onOpenVersions={() => undefined}
/>
}
@@ -1,4 +1,5 @@
import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { AgentPermission } from '@/features/agent-v2/acl'
import useDocumentTitle from '@/hooks/use-document-title'
import { consoleQuery } from '@/service/console'
@@ -78,6 +79,30 @@ describe('AgentDetailLayout', () => {
await waitFor(() => expect(router.replace).toHaveBeenCalledWith('/agents/agent-1/configure'))
})
it('retries a failed access page request at the detail boundary', async () => {
const user = userEvent.setup()
location.pathname = '/agents/agent-1/access'
const { wrapper, queryClient } = setup()
queryClient.setDefaultOptions({ queries: { retry: false, retryOnMount: false } })
queryClient
.getQueryCache()
.find({ queryKey: detailKey })!
.setState({
data: undefined,
error: new Error('Unavailable'),
status: 'error',
fetchStatus: 'idle',
})
vi.mocked(fetch).mockResolvedValueOnce(Response.json(createAgentFixture()))
render(content, { wrapper })
expect(await screen.findByRole('alert')).toHaveTextContent('agentV2.roster.loadingError')
expect(screen.queryByText('Agent detail content')).not.toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'common.operation.retry' }))
expect(await screen.findByText('Agent detail content')).toBeVisible()
expect(fetch).toHaveBeenCalledTimes(1)
expect(router.replace).not.toHaveBeenCalled()
})
it('redirects to the roster when the agent no longer exists', async () => {
const { wrapper, queryClient } = setup()
queryClient.setDefaultOptions({ queries: { retry: false, retryOnMount: false } })
@@ -7,10 +7,8 @@ import { useTranslation } from 'react-i18next'
import { getAgentACLCapabilities } from '@/features/agent-v2/acl'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { AccessMode, isAccessMode } from '@/models/access-control'
import {
useAppWhiteListSubjects,
useGetUserCanAccessApp,
} from '@/service/access-control/use-app-access-control'
import { useAppWhiteListSubjects } from '@/service/access-control/use-app-access-control'
import { useWebAppAccessPermission } from '../../web-app-access'
const ACCESS_MODE_ICON_MAP: Record<AccessMode, string> = {
[AccessMode.ORGANIZATION]: 'i-ri-building-line',
@@ -39,15 +37,7 @@ export function useWebAppAccessControl(
})
const { canManageAccessPoint: canManage } = getAgentACLCapabilities(agent?.permission_keys)
const hasAccessControl = Boolean(webAppAuthEnabled && appId && accessMode)
const { data: userCanAccessApp, refetch: refetchUserCanAccessApp } = useGetUserCanAccessApp({
appId,
enabled: Boolean(webAppAuthEnabled && appId),
})
const accessPermission = {
noAccessPermission:
webAppAuthEnabled && accessMode !== AccessMode.EXTERNAL_MEMBERS && !userCanAccessApp?.result,
refetchUserCanAccessApp,
}
const accessPermission = useWebAppAccessPermission(agent)
const { data: accessSubjects } = useAppWhiteListSubjects(
appId,
hasAccessControl && canManage && accessMode === AccessMode.SPECIFIC_GROUPS_MEMBERS,
@@ -23,6 +23,7 @@ import { getAgentACLCapabilities } from '@/features/agent-v2/acl'
import dynamic from '@/next/dynamic'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { getAgentWebAppUrl } from '../../web-app-access'
import { useWebAppAccessControl } from './use-web-app-access-control'
const AccessControl = dynamic(() => import('@/app/components/app/app-access-control'), {
@@ -434,13 +435,3 @@ function getSettingsIcon(agent: AgentAppDetailWithSite) {
icon_url: null,
}
}
function getAgentWebAppUrl(agent?: AgentAppDetailWithSite) {
const site = agent?.site
const token = site?.access_token ?? site?.code
if (!token) return ''
const baseUrl =
site?.app_base_url || (typeof window === 'undefined' ? '' : window.location.origin)
return `${baseUrl.replace(/\/$/, '')}/agent/${token}`
}
@@ -1,4 +1,5 @@
import type { PropsWithChildren } from 'react'
import type { AgentConfigurePublishResult } from '../use-agent-configure-sync'
import type { ToolWithProvider } from '@/app/components/workflow/types'
import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
@@ -92,6 +93,7 @@ type PublishAgentVariables = {
}
type PublishAgentResponse = {
publication_kind: 'first' | 'update'
active_config_snapshot: Record<string, unknown> | null
active_config_snapshot_id: string
result: string
@@ -104,6 +106,7 @@ const publishAgentMutationFn = vi.hoisted(() =>
},
active_config_snapshot_id: 'snapshot-1',
result: 'success',
publication_kind: 'update',
})),
)
@@ -1075,7 +1078,7 @@ describe('useAgentConfigureSync', () => {
}),
)
await act(async () => {
await result.current.publishDraft()
await expect(result.current.publishDraft()).resolves.toBe(false)
})
expect(publishAgentMutationFn).not.toHaveBeenCalled()
expect(toastMock.error).toHaveBeenCalledWith('common.modelProvider.selectModel')
@@ -1149,7 +1152,7 @@ describe('useAgentConfigureSync', () => {
})
await act(async () => {
await result.current.publishDraft()
await expect(result.current.publishDraft()).resolves.toMatchObject({ kind: 'update' })
})
expect(composerPutMutationFn).toHaveBeenCalledWith(
@@ -1180,7 +1183,7 @@ describe('useAgentConfigureSync', () => {
app_name: 'Agent',
app_mode: 'agent-v2',
})
expect(toastMock.success).toHaveBeenCalledWith('common.api.actionSuccess')
expect(toastMock.success).not.toHaveBeenCalled()
})
it('should toast and skip publish when no model is configured', async () => {
@@ -1494,7 +1497,7 @@ describe('useAgentConfigureSync', () => {
const { result } = renderUseAgentConfigureSync({
currentModel: configuredModel,
})
let publishPromise!: Promise<void>
let publishPromise!: Promise<AgentConfigurePublishResult | false>
act(() => {
publishPromise = result.current.publishDraft()
})
@@ -1511,6 +1514,7 @@ describe('useAgentConfigureSync', () => {
active_config_snapshot: {},
active_config_snapshot_id: 'snapshot-1',
result: 'success',
publication_kind: 'update',
})
await publishPromise
await vi.advanceTimersByTimeAsync(0)
@@ -1532,7 +1536,7 @@ describe('useAgentConfigureSync', () => {
})
})
let publishPromise!: Promise<void>
let publishPromise!: Promise<AgentConfigurePublishResult | false>
act(() => {
publishPromise = result.current.publishDraft()
})
@@ -1559,6 +1563,7 @@ describe('useAgentConfigureSync', () => {
active_config_snapshot: {},
active_config_snapshot_id: 'snapshot-1',
result: 'success',
publication_kind: 'update',
})
await publishPromise
await vi.advanceTimersByTimeAsync(5000)
@@ -1591,7 +1596,7 @@ describe('useAgentConfigureSync', () => {
})
})
let publishPromise!: Promise<void>
let publishPromise!: Promise<AgentConfigurePublishResult | false>
act(() => {
publishPromise = result.current.publishDraft()
})
@@ -1634,6 +1639,7 @@ describe('useAgentConfigureSync', () => {
active_config_snapshot: {},
active_config_snapshot_id: 'snapshot-1',
result: 'success',
publication_kind: 'update',
})
await publishPromise
await Promise.resolve()
@@ -1653,7 +1659,7 @@ describe('useAgentConfigureSync', () => {
})
})
let publishPromise!: Promise<void>
let publishPromise!: Promise<AgentConfigurePublishResult | false>
act(() => {
publishPromise = result.current.publishDraft()
})
@@ -1726,11 +1732,11 @@ describe('useAgentConfigureSync', () => {
permission_keys: [AgentPermission.ReleaseAndVersion],
})
await act(async () => {
await result.current.publishDraft()
await expect(result.current.publishDraft()).resolves.toMatchObject({ kind: 'update' })
})
expect(composerPutMutationFn).not.toHaveBeenCalled()
expect(publishAgentMutationFn).toHaveBeenCalledOnce()
expect(toastMock.success).toHaveBeenCalledOnce()
expect(toastMock.success).not.toHaveBeenCalled()
})
it('does not flush dirty configuration after edit permission is revoked before unmount', async () => {
@@ -1750,7 +1756,7 @@ describe('useAgentConfigureSync', () => {
const saving = createDeferredPromise<{ agent_soul: Record<string, unknown> }>()
composerPutMutationFn.mockReturnValueOnce(saving.promise)
const { queryClient, result } = renderUseAgentConfigureSync({ currentModel: configuredModel })
let publishing!: Promise<void>
let publishing!: Promise<AgentConfigurePublishResult | false>
act(() => {
publishing = result.current.publishDraft()
})
@@ -0,0 +1,133 @@
import { zAgentAppComposerResponse } from '@dify/contracts/api/console/agent/zod.gen'
import { QueryClientProvider } from '@tanstack/react-query'
import { createStore, Provider } from 'jotai'
import { useState } from 'react'
import { userEvent } from 'vite-plus/test/browser'
import { render } from 'vitest-browser-react'
import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { consoleQuery } from '@/service/console'
import { createSystemFeaturesFixture } from '@/test/console/system-features'
import { createAgentFixture } from '@/test/fixtures/agent'
import { createTestQueryClient } from '@/test/query-client'
import { AgentConfigurePublishBar } from '../publish-bar'
vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn() } }))
vi.mock('@/hooks/use-format-time-from-now', () => ({
useFormatTimeFromNow: () => ({ formatTimeFromNow: () => 'just now' }),
}))
vi.mock('@/hooks/use-timestamp', () => ({ default: () => ({ formatTime: () => '' }) }))
vi.mock('../../version-restore', () => ({ AgentVersionRestore: () => null }))
const successTitle = 'agentV2.agentDetail.configure.publishSuccess.updateTitle'
const dismissLabel = 'agentV2.agentDetail.configure.publishSuccess.dismiss'
async function setup() {
const queryClient = createTestQueryClient()
const store = createStore()
const input = { params: { agent_id: 'agent-1' } }
queryClient.setQueryData(
consoleQuery.agent.byAgentId.composer.get.queryKey({ input }),
zAgentAppComposerResponse.parse({
active_config_is_published: false,
active_config_snapshot: null,
agent: {
id: 'agent-1',
name: 'Agent',
description: '',
scope: 'roster',
status: 'active',
hidden_app_backed: false,
},
agent_soul: { schema_version: 1, config_note: '' },
save_options: ['save_to_current_version'],
variant: 'agent_app',
}),
)
queryClient.setQueryData(
consoleQuery.agent.byAgentId.get.queryKey({ input }),
createAgentFixture({
access_ready: true,
enable_site: true,
site: {
access_token: 'published-token',
app_base_url: 'https://apps.example.test',
icon_url: null,
},
}),
)
queryClient.setQueryData(
systemFeaturesQueryOptions().queryKey,
createSystemFeaturesFixture({ webapp_auth: { enabled: false } }),
)
// Only the reference lookup crosses the network in this publication UI test.
vi.spyOn(globalThis, 'fetch').mockImplementation(async () => Response.json({ data: [] }))
let completePublish!: () => void
const published = new Promise<void>((resolve) => {
completePublish = resolve
})
function Harness() {
const [isPublishing, setIsPublishing] = useState(false)
return (
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<input aria-label="Other editor" />
<div className="w-96">
<AgentConfigurePublishBar
agentId="agent-1"
isPublishing={isPublishing}
onPublish={async () => {
const draft = store.get(agentComposerDraftAtom)
setIsPublishing(true)
await published
setIsPublishing(false)
return { kind: 'update', draft }
}}
/>
</div>
</Provider>
</QueryClientProvider>
)
}
const screen = await render(<Harness />)
return { screen, completePublish, queryClient }
}
afterEach(() => vi.restoreAllMocks())
it('preserves keyboard focus through loading, publication, and dismissal', async () => {
// Native tab order, disabled-button focus, and focus-visible require a browser.
const { screen, completePublish, queryClient } = await setup()
const editor = screen.getByRole('textbox', { name: 'Other editor' })
await editor.click()
await userEvent.tab()
await expect.element(screen.getByRole('button', { name: /versionHistory$/ })).toHaveFocus()
await userEvent.tab()
const publish = screen.getByRole('button', { name: /agentV2.agentDetail.publish/ })
await expect.element(publish).toHaveFocus()
const bar = publish.element().closest('[tabindex="-1"]')!
const liveRegion = bar.querySelector('[role="status"][aria-atomic="true"]')!
expect(liveRegion.textContent).toBe('')
await userEvent.keyboard('{Enter}')
await expect.element(screen.getByRole('button', { name: /publishing/ })).toHaveFocus()
completePublish()
await expect.poll(() => liveRegion.textContent).toBe(successTitle)
await expect.poll(() => document.activeElement).toBe(bar)
expect(bar.matches(':focus-visible')).toBe(true)
expect(bar.querySelector('[role="status"]')).toBe(liveRegion)
await userEvent.tab()
const dismiss = screen.getByRole('button', { name: dismissLabel })
await expect.element(dismiss).toHaveFocus()
await userEvent.tab()
await expect.element(screen.getByRole('link', { name: /accessMethods$/ })).toHaveFocus()
await userEvent.tab()
await expect.element(screen.getByRole('link', { name: /openWebApp$/ })).toHaveFocus()
await userEvent.tab({ shift: true })
await userEvent.tab({ shift: true })
await userEvent.keyboard('{Enter}')
await expect.poll(() => document.activeElement).toBe(bar)
expect(liveRegion.textContent).toBe('')
await userEvent.tab()
await expect.element(screen.getByRole('button', { name: /versionHistory$/ })).toHaveFocus()
queryClient.clear()
})
@@ -1,4 +1,5 @@
import type {
AgentAppDetailWithSite,
AgentConfigSnapshotSummaryResponse,
AgentReferencingWorkflowResponse,
} from '@dify/contracts/api/console/agent/types.gen'
@@ -14,6 +15,7 @@ import {
agentComposerSavedDraftAtom,
} from '@/features/agent-v2/agent-composer/store'
import { agentComposerPromptAtom } from '@/features/agent-v2/agent-composer/store-modules/prompt'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { consoleQuery } from '@/service/console'
import { createNuqsTestWrapper } from '@/test/nuqs-testing'
import { AgentConfigurePublishBar } from '../publish-bar'
@@ -63,6 +65,8 @@ const workflowReferences = vi.hoisted(() => ({
data: [] as AgentReferencingWorkflowResponse[],
shouldFail: false,
}))
const agentDetail = vi.hoisted(() => ({ data: {} as Partial<AgentAppDetailWithSite> }))
const composerQuery = vi.hoisted(() => ({
data: undefined as unknown,
shouldFail: false,
@@ -102,7 +106,7 @@ vi.mock('@/hooks/use-timestamp', () => ({
vi.mock('@/features/system-features/client', () => ({
systemFeaturesQueryOptions: () => ({
queryKey: ['system-features'],
queryFn: async () => ({ deployment_edition: 'CLOUD' }),
queryFn: async () => ({ deployment_edition: 'CLOUD', webapp_auth: { enabled: false } }),
}),
}))
@@ -121,6 +125,10 @@ vi.mock('@/service/console', () => ({
byAgentId: {
get: {
queryKey: ({ input }: { input: { params: { agent_id: string } } }) => ['agent', input],
queryOptions: ({ input }: { input: { params: { agent_id: string } } }) => ({
queryKey: ['agent', input],
queryFn: async () => agentDetail.data,
}),
},
composer: {
get: {
@@ -237,13 +245,14 @@ function createDeferredPromise() {
}
function renderPublishBar({
agent = {},
activeConfigIsPublished,
activeConfigSnapshot,
draftSavedAt,
composerQueryAvailable = true,
composerQueryFails = false,
isPublishing,
onPublish = vi.fn<PublishHandler>(),
onPublish: providedOnPublish,
onExitVersions = vi.fn(),
onVersionRestored = vi.fn(),
prompt = '',
@@ -251,6 +260,7 @@ function renderPublishBar({
setupStore,
usedByAppReferences = [],
}: {
agent?: Partial<AgentAppDetailWithSite>
activeConfigIsPublished?: boolean
activeConfigSnapshot?: AgentConfigSnapshotSummaryResponse | null
draftSavedAt?: number
@@ -265,6 +275,7 @@ function renderPublishBar({
setupStore?: (store: ReturnType<typeof createStore>) => void
usedByAppReferences?: AgentReferencingWorkflowResponse[]
} = {}) {
agentDetail.data = agent
workflowReferences.data = usedByAppReferences
composerQuery.shouldFail = composerQueryFails
const queryClient = new QueryClient({
@@ -277,6 +288,15 @@ function renderPublishBar({
billing: { subscription: { plan: restoreAccess.plan } },
})
const { wrapper: NuqsWrapper } = createNuqsTestWrapper({ onUrlUpdate: restoreAccess.onUrlUpdate })
queryClient.setQueryData<unknown>(
consoleQuery.agent.byAgentId.get.queryOptions({ input: { params: { agent_id: 'agent-1' } } })
.queryKey,
agent,
)
queryClient.setQueryData<unknown>(systemFeaturesQueryOptions().queryKey, {
deployment_edition: 'CLOUD',
webapp_auth: { enabled: false },
})
const store = createStore()
store.set(agentComposerPromptAtom, prompt)
setupStore?.(store)
@@ -307,6 +327,13 @@ function renderPublishBar({
queryClient.setQueryData(composerQueryKey, composerState)
}
const onPublish =
providedOnPublish ??
vi.fn<PublishHandler>(async () => ({
kind: 'update',
draft: store.get(agentComposerDraftAtom),
}))
const renderPublishBarTree = (nextProps?: {
isPublishing?: boolean
selectedVersionSnapshot?: AgentConfigSnapshotSummaryResponse | null
@@ -337,6 +364,7 @@ function renderPublishBar({
return {
...view,
queryClient,
store,
onExitVersions,
onPublish,
onVersionRestored,
@@ -939,13 +967,18 @@ describe('AgentConfigurePublishBar', () => {
it('should publish from the fixed toolbar action after affected workflow details expand', async () => {
const publishDeferred = createDeferredPromise()
const onPublish = vi.fn<PublishHandler>(() => publishDeferred.promise)
const { rerender, rerenderPublishBar } = renderPublishBar({
const onPublish = vi.fn<PublishHandler>()
const { store, rerender, rerenderPublishBar } = renderPublishBar({
activeConfigSnapshot,
onPublish,
prompt: 'Updated system prompt',
usedByAppReferences: publishedReferences,
})
onPublish.mockImplementation(async () => {
const draft = store.get(agentComposerDraftAtom)
await publishDeferred.promise
return { kind: 'update', draft }
})
fireEvent.click(
screen.getByRole('button', {
@@ -1102,4 +1135,228 @@ describe('AgentConfigurePublishBar', () => {
).not.toBeInTheDocument()
expect(onPublish).toHaveBeenCalledTimes(1)
})
describe('publish success guidance', () => {
const successKey = 'agentV2.agentDetail.configure.publishSuccess.'
const readyAgent = {
access_ready: true,
enable_site: true,
site: {
access_token: 'published-token',
app_base_url: 'https://apps.example.test',
icon_url: null,
},
}
it.each([
['first', 'firstTitle'],
['update', 'updateTitle'],
] as const)(
'uses the publication result even when cached availability disagrees (%s)',
async (kind, title) => {
const user = userEvent.setup()
const view = renderPublishBar({ agent: { ...readyAgent, access_ready: kind === 'first' } })
view.onPublish.mockImplementation(async () => {
agentDetail.data = readyAgent
view.queryClient.setQueryData<unknown>(
consoleQuery.agent.byAgentId.get.queryOptions({
input: { params: { agent_id: 'agent-1' } },
}).queryKey,
readyAgent,
)
return { kind, draft: view.store.get(agentComposerDraftAtom) }
})
expect(screen.queryByText(successKey + title, { selector: 'p' })).not.toBeInTheDocument()
await user.click(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ }))
expect(await screen.findByText(successKey + title, { selector: 'p' })).toBeVisible()
const link = screen.getByRole('link', { name: `${successKey}openWebApp` })
expect(link).toHaveAttribute('href', 'https://apps.example.test/agent/published-token')
expect(link).toHaveAttribute('target', '_blank')
expect(screen.getByRole('link', { name: `${successKey}accessMethods` })).toHaveAttribute(
'href',
'/agents/agent-1/access',
)
},
)
it.each(['dismiss', 'openWebApp', 'accessMethods'] as const)(
'consumes the guidance after %s and allows the next publication to show it again',
async (action) => {
const user = userEvent.setup()
renderPublishBar({ agent: readyAgent })
await user.click(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ }))
await screen.findByText(`${successKey}updateTitle`, { selector: 'p' })
const bar = document.activeElement as HTMLElement
expect(bar).toContainElement(
screen.getByText(`${successKey}updateTitle`, { selector: 'p' }),
)
const focus = vi.spyOn(bar, 'focus')
const control = screen.getByRole(action === 'dismiss' ? 'button' : 'link', {
name: successKey + action,
})
// Navigation is verified in Chrome; this test exercises the guidance lifetime.
if (action !== 'dismiss')
control.addEventListener('click', (event) => event.preventDefault())
await user.click(control)
if (action === 'accessMethods') {
expect(focus).not.toHaveBeenCalled()
} else {
expect(bar).toHaveFocus()
expect(focus).toHaveBeenCalledWith({ preventScroll: true })
}
focus.mockRestore()
expect(
screen.queryByText(`${successKey}updateTitle`, { selector: 'p' }),
).not.toBeInTheDocument()
expect(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ })).toBeVisible()
await user.click(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ }))
expect(await screen.findByText(`${successKey}updateTitle`, { selector: 'p' })).toBeVisible()
},
)
it('does not steal focus moved outside the bar while publishing', async () => {
const user = userEvent.setup()
const deferred = createDeferredPromise()
const onPublish = vi.fn<PublishHandler>()
const { store } = renderPublishBar({ agent: readyAgent, onPublish })
render(<input aria-label="Other editor" />)
onPublish.mockImplementation(async () => {
const draft = store.get(agentComposerDraftAtom)
await deferred.promise
return { kind: 'update', draft }
})
await user.click(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ }))
await waitFor(() => expect(onPublish).toHaveBeenCalledOnce())
const editor = screen.getByRole('textbox', { name: 'Other editor' })
await user.click(editor)
await act(async () => {
deferred.resolve()
await deferred.promise
})
expect(await screen.findByText(`${successKey}updateTitle`, { selector: 'p' })).toBeVisible()
expect(editor).toHaveFocus()
})
it('does not reopen after an edit is autosaved or undone', async () => {
const user = userEvent.setup()
const { store } = renderPublishBar({ agent: readyAgent })
await user.click(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ }))
await screen.findByText(`${successKey}updateTitle`, { selector: 'p' })
const publishedDraft = store.get(agentComposerDraftAtom)
act(() => {
store.set(agentComposerPromptAtom, 'New edit')
})
expect(
screen.queryByText(`${successKey}updateTitle`, { selector: 'p' }),
).not.toBeInTheDocument()
expect(
screen.getByText('agentV2.agentDetail.configure.publishBar.unpublishedChanges'),
).toBeVisible()
act(() => {
store.set(agentComposerSavedDraftAtom, store.get(agentComposerDraftAtom))
})
expect(
screen.queryByText(`${successKey}updateTitle`, { selector: 'p' }),
).not.toBeInTheDocument()
act(() => store.set(agentComposerDraftAtom, publishedDraft))
expect(
screen.queryByText(`${successKey}updateTitle`, { selector: 'p' }),
).not.toBeInTheDocument()
})
it('does not show success when publishing is blocked by validation', async () => {
const user = userEvent.setup()
const onPublish = vi.fn<PublishHandler>().mockResolvedValue(false)
renderPublishBar({ agent: readyAgent, onPublish })
await user.click(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ }))
await waitFor(() => expect(onPublish).toHaveBeenCalledOnce())
expect(
screen.queryByText(`${successKey}updateTitle`, { selector: 'p' }),
).not.toBeInTheDocument()
})
it('ignores repeated publication requests while the first request is pending', async () => {
const user = userEvent.setup()
const deferred = createDeferredPromise()
const onPublish = vi.fn<PublishHandler>()
const { store } = renderPublishBar({ agent: readyAgent, onPublish })
onPublish.mockImplementation(async () => {
const draft = store.get(agentComposerDraftAtom)
await deferred.promise
return { kind: 'update', draft }
})
const publishButton = screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ })
await user.dblClick(publishButton)
await waitFor(() => expect(onPublish).toHaveBeenCalledOnce())
await act(async () => {
deferred.resolve()
await deferred.promise
})
expect(await screen.findByText(`${successKey}updateTitle`, { selector: 'p' })).toBeVisible()
expect(onPublish).toHaveBeenCalledOnce()
})
it('does not show stale success over edits made during publication', async () => {
const user = userEvent.setup()
const deferred = createDeferredPromise()
const onPublish = vi.fn<PublishHandler>()
const { store } = renderPublishBar({ agent: readyAgent, onPublish })
onPublish.mockImplementation(async () => {
const draft = store.get(agentComposerDraftAtom)
await deferred.promise
return { kind: 'update', draft }
})
await user.click(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ }))
await waitFor(() => expect(onPublish).toHaveBeenCalledOnce())
act(() => {
store.set(agentComposerPromptAtom, 'Edited while publishing')
})
await act(async () => {
deferred.resolve()
await deferred.promise
})
expect(
screen.queryByText(`${successKey}updateTitle`, { selector: 'p' }),
).not.toBeInTheDocument()
expect(
screen.getByText('agentV2.agentDetail.configure.publishBar.unpublishedChanges'),
).toBeVisible()
})
it.each([
{ ...readyAgent, enable_site: false },
{ ...readyAgent, access_ready: false },
{ ...readyAgent, site: null },
])('retains only access methods when the Web App cannot open (%j)', async (agent) => {
const user = userEvent.setup()
renderPublishBar({ agent })
await user.click(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ }))
expect(await screen.findByRole('link', { name: `${successKey}accessMethods` })).toBeVisible()
expect(
screen.queryByRole('link', { name: `${successKey}openWebApp` }),
).not.toBeInTheDocument()
expect(screen.getByText(`${successKey}accessDescription`)).toBeVisible()
})
it('does not show guidance just because an existing version is published', () => {
renderPublishBar({ agent: readyAgent, activeConfigIsPublished: true, activeConfigSnapshot })
expect(
screen.queryByText(`${successKey}updateTitle`, { selector: 'p' }),
).not.toBeInTheDocument()
expect(screen.getByText('agentV2.agentDetail.configure.publishBar.upToDate')).toBeVisible()
})
it('clears guidance when entering version history', async () => {
const user = userEvent.setup()
const { rerender, rerenderPublishBar } = renderPublishBar({ agent: readyAgent })
await user.click(screen.getByRole('button', { name: /agentV2\.agentDetail\.publish/ }))
await screen.findByText(`${successKey}updateTitle`, { selector: 'p' })
rerender(rerenderPublishBar({ selectedVersionSnapshot: activeConfigSnapshot }))
rerender(rerenderPublishBar({ selectedVersionSnapshot: null }))
expect(
screen.queryByText(`${successKey}updateTitle`, { selector: 'p' }),
).not.toBeInTheDocument()
})
})
})
@@ -9,14 +9,14 @@ export function AgentOrchestrateBottomActions({
shrinkOnOpen?: boolean
}) {
return (
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex h-18 flex-col items-center justify-end px-4 pt-4 pb-2 transition-[height] duration-150 ease-out has-data-open:h-76.75 motion-reduce:transition-none">
<div className="pointer-events-none sticky bottom-0 flex shrink-0 flex-col items-center px-4 pt-4 pb-2">
<div
aria-hidden
className="pointer-events-none absolute inset-0 bg-linear-to-t from-components-panel-bg to-components-panel-bg-transparent mask-[linear-gradient(to_top,black,transparent)] backdrop-blur-[2px] [-webkit-mask-image:linear-gradient(to_top,black,transparent)]"
/>
<div
className={cn(
'relative flex w-full max-w-126.5 flex-col items-center justify-end transition-[max-width] duration-150 ease-out motion-reduce:transition-none',
'pointer-events-auto relative flex w-full max-w-126.5 flex-col items-center justify-end transition-[max-width] duration-150 ease-out motion-reduce:transition-none',
shrinkOnOpen && 'has-data-open:max-w-96',
)}
>
@@ -5,6 +5,7 @@ import type {
AgentConfigSnapshotSummaryResponse,
} from '@dify/contracts/api/console/agent/types.gen'
import type { ReactNode } from 'react'
import type { AgentConfigurePublishResult } from '../../use-agent-configure-sync'
import type { AgentBuildDraftChangedKey } from './build-draft-changes-context'
import type { AgentComposerModel } from '@/features/agent-v2/agent-composer/form-state'
import { cn } from '@langgenius/dify-ui/cn'
@@ -56,7 +57,7 @@ type AgentOrchestratePanelProps = {
headerAction?: ReactNode
bottomAction?: ReactNode
onSelectModel: (model: AgentComposerModel) => void
onPublish?: () => void | Promise<void>
onPublish?: () => Promise<AgentConfigurePublishResult | false>
onExitVersions?: () => void
onOpenVersions?: () => void
onVersionRestored?: () => void | Promise<void>
@@ -102,7 +103,6 @@ export function AgentOrchestratePanel({
onVersionRestored={onVersionRestored}
/>
) : null)
const hasBottomAction = !!orchestrateBottomAction
const draftType = isBuildDraftActive ? ('debug_build' as const) : ('draft' as const)
const configApiContext = useMemo(
() =>
@@ -150,36 +150,40 @@ export function AgentOrchestratePanel({
<ScrollAreaViewport
aria-label={showHeader ? undefined : orchestrateLabel}
aria-labelledby={showHeader ? orchestrateHeadingId : undefined}
className="overscroll-contain"
className="overscroll-none"
role="region"
>
<ScrollAreaContent className={cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20')}>
<AgentConfigApiContextProvider value={configApiContext}>
<AgentOrchestrateAddActionsProvider>
<AgentBuildDraftChangedKeysProvider
changedKeys={
isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS
}
>
<AgentModelField currentModel={currentModel} onSelect={onSelectModel} />
<AgentPromptEditor />
<AgentSkills />
<AgentFiles />
<AgentTools />
{ENABLE_AGENT_KNOWLEDGE_RETRIEVAL && <AgentKnowledgeRetrieval />}
<AgentAdvancedSettings />
</AgentBuildDraftChangedKeysProvider>
</AgentOrchestrateAddActionsProvider>
</AgentConfigApiContextProvider>
<ScrollAreaContent className="flex min-h-full flex-col" style={{ minWidth: 0 }}>
<div className="flex-1 px-4 py-3">
<AgentConfigApiContextProvider value={configApiContext}>
<AgentOrchestrateAddActionsProvider>
<AgentBuildDraftChangedKeysProvider
changedKeys={
isBuildDraftActive
? buildDraftChangedKeys
: EMPTY_BUILD_DRAFT_CHANGED_KEYS
}
>
<AgentModelField currentModel={currentModel} onSelect={onSelectModel} />
<AgentPromptEditor />
<AgentSkills />
<AgentFiles />
<AgentTools />
{ENABLE_AGENT_KNOWLEDGE_RETRIEVAL && <AgentKnowledgeRetrieval />}
<AgentAdvancedSettings />
</AgentBuildDraftChangedKeysProvider>
</AgentOrchestrateAddActionsProvider>
</AgentConfigApiContextProvider>
</div>
{orchestrateBottomAction ? (
<AgentOrchestrateBottomActions shrinkOnOpen={!bottomAction}>
{orchestrateBottomAction}
</AgentOrchestrateBottomActions>
) : null}
</ScrollAreaContent>
</ScrollAreaViewport>
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
{orchestrateBottomAction ? (
<AgentOrchestrateBottomActions shrinkOnOpen={!bottomAction}>
{orchestrateBottomAction}
</AgentOrchestrateBottomActions>
) : null}
<ScrollAreaScrollbar>
<ScrollAreaThumb />
</ScrollAreaScrollbar>
@@ -6,6 +6,7 @@ import type {
AgentReferencingWorkflowsResponse,
} from '@dify/contracts/api/console/agent/types.gen'
import type { Hotkey } from '@tanstack/react-hotkeys'
import type { AgentConfigurePublishResult } from '../../../use-agent-configure-sync'
import { Button } from '@langgenius/dify-ui/button'
import { Collapsible, CollapsiblePanel } from '@langgenius/dify-ui/collapsible'
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
@@ -14,15 +15,19 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/too
import { formatForDisplay, useHotkey } from '@tanstack/react-hotkeys'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import { useId, useState } from 'react'
import { useId, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from '@/app/notifications'
import { isAgentComposerDirtyAtom } from '@/features/agent-v2/agent-composer/store'
import {
agentComposerDraftAtom,
isAgentComposerDirtyAtom,
} from '@/features/agent-v2/agent-composer/store'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import useTimestamp from '@/hooks/use-timestamp'
import { consoleQuery } from '@/service/console'
import { AgentVersionRestore } from '../../version-restore'
import { AgentPublishImpactDetails } from './publish-impact-details'
import { AgentPublishSuccess } from './publish-success'
const PUBLISH_AGENT_HOTKEY = 'Mod+Shift+P' satisfies Hotkey
@@ -30,6 +35,7 @@ type AgentConfigurePublishState = 'draft' | 'publishing' | 'published' | 'unpubl
type PublishBarMode =
| { status: 'compact' }
| ({ status: 'success' } & AgentConfigurePublishResult)
| { status: 'confirmingImpact'; references: AgentReferencingWorkflowResponse[] }
type AgentConfigurePublishBarProps = {
@@ -37,7 +43,7 @@ type AgentConfigurePublishBarProps = {
agentName?: string | null
isPublishing?: boolean
selectedVersionSnapshot?: AgentConfigSnapshotSummaryResponse | null
onPublish?: () => void | Promise<void>
onPublish?: () => Promise<AgentConfigurePublishResult | false>
onExitVersions?: () => void
onOpenVersions?: () => void
onVersionRestored?: () => void | Promise<void>
@@ -91,6 +97,9 @@ export function AgentConfigurePublishBar({
const { t: tCommon } = useTranslation('common')
const { formatTimeFromNow } = useFormatTimeFromNow()
const queryClient = useQueryClient()
const draft = useAtomValue(agentComposerDraftAtom)
const barRef = useRef<HTMLDivElement>(null)
const publishRequestPendingRef = useRef(false)
const [publishBarMode, setPublishBarMode] = useState<PublishBarMode>({ status: 'compact' })
const composerQuery = useQuery(
consoleQuery.agent.byAgentId.composer.get.queryOptions({
@@ -136,11 +145,18 @@ export function AgentConfigurePublishBar({
useQuery(workflowReferencesQueryOptions)
const canPublish = publishIsAvailable && !isPublishing
const preserveBarFocus = () => {
const bar = barRef.current
if (bar?.contains(bar.ownerDocument.activeElement)) bar.focus({ preventScroll: true })
}
const handlePublish = async () => {
if (!canPublish) return
await onPublish?.()
setPublishBarMode({ status: 'compact' })
const published = await onPublish?.()
if (!published) return
preserveBarFocus()
setPublishBarMode({ status: 'success', ...published })
}
const handlePublishRequest = async () => {
@@ -172,7 +188,13 @@ export function AgentConfigurePublishBar({
}
const requestPublish = () => {
void handlePublishRequest().catch(() => undefined)
if (publishRequestPendingRef.current) return
publishRequestPendingRef.current = true
void handlePublishRequest()
.catch(() => undefined)
.finally(() => {
publishRequestPendingRef.current = false
})
}
useHotkey(
@@ -187,6 +209,19 @@ export function AgentConfigurePublishBar({
},
)
// Consume the notice instead of just hiding it, so undoing edits cannot bring it back.
if (
publishBarMode.status === 'success' &&
(selectedVersionSnapshot || publishBarMode.draft !== draft)
) {
setPublishBarMode({ status: 'compact' })
}
const dismissSuccess = () => {
preserveBarFocus()
setPublishBarMode({ status: 'compact' })
}
if (selectedVersionSnapshot) {
return (
<AgentVersionRestore
@@ -273,29 +308,50 @@ export function AgentConfigurePublishBar({
return (
<Collapsible
ref={barRef}
tabIndex={-1}
open={isConfirmingImpact}
className="group/publish-bar pointer-events-auto w-full overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]"
className="group/publish-bar pointer-events-auto w-full overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px] focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden"
>
<CollapsiblePanel className="system-sm-regular text-text-secondary">
<AgentPublishImpactDetails
publishActionLabel={currentStateMeta.actionLabel}
agentName={agentName}
references={impactReferences}
{/* Mount the live region before its message so assistive technology observes the update. */}
<span role="status" aria-atomic="true" className="sr-only">
{publishBarMode.status === 'success'
? publishBarMode.kind === 'first'
? t(($) => $['agentDetail.configure.publishSuccess.firstTitle'])
: t(($) => $['agentDetail.configure.publishSuccess.updateTitle'])
: null}
</span>
{publishBarMode.status === 'success' ? (
<AgentPublishSuccess
agentId={agentId}
kind={publishBarMode.kind}
onDismiss={dismissSuccess}
onAccessMethods={() => setPublishBarMode({ status: 'compact' })}
/>
</CollapsiblePanel>
<PublishBarActions
actionIcon={currentStateMeta.actionIcon}
actionLabel={currentStateMeta.actionLabel}
dotStatus={currentStateMeta.dotStatus}
isPublishing={isPublishing}
metaLabel={currentStateMeta.metaLabel}
showShortcut={currentStateMeta.showShortcut}
statusLabel={currentStateMeta.statusLabel}
publishIsAvailable={publishIsAvailable}
onCancelImpact={() => setPublishBarMode({ status: 'compact' })}
onOpenVersions={() => onOpenVersions?.()}
onPublishRequest={requestPublish}
/>
) : (
<>
<CollapsiblePanel className="system-sm-regular text-text-secondary">
<AgentPublishImpactDetails
publishActionLabel={currentStateMeta.actionLabel}
agentName={agentName}
references={impactReferences}
/>
</CollapsiblePanel>
<PublishBarActions
actionIcon={currentStateMeta.actionIcon}
actionLabel={currentStateMeta.actionLabel}
dotStatus={currentStateMeta.dotStatus}
isPublishing={isPublishing}
metaLabel={currentStateMeta.metaLabel}
showShortcut={currentStateMeta.showShortcut}
statusLabel={currentStateMeta.statusLabel}
publishIsAvailable={publishIsAvailable}
onCancelImpact={() => setPublishBarMode({ status: 'compact' })}
onOpenVersions={() => onOpenVersions?.()}
onPublishRequest={requestPublish}
/>
</>
)}
</Collapsible>
)
}
@@ -338,13 +394,21 @@ function PublishBarActions({
<span className="flex size-4 shrink-0 items-center justify-center">
<StatusDot size="small" status={dotStatus} />
</span>
<span className="shrink-0">{statusLabel}</span>
<span aria-hidden className="shrink-0">
·
</span>
<Tooltip>
<TooltipTrigger render={<span className="min-w-0 truncate">{metaLabel}</span>} />
<TooltipContent>{metaLabel}</TooltipContent>
<TooltipTrigger
render={
<span className="min-w-0 truncate">
<span>{statusLabel}</span>
<span aria-hidden> · </span>
<span>{metaLabel}</span>
</span>
}
/>
<TooltipContent>
<span>{statusLabel}</span>
<span aria-hidden> · </span>
<span>{metaLabel}</span>
</TooltipContent>
</Tooltip>
</div>
<button
@@ -0,0 +1,90 @@
'use client'
import { buttonVariants } from '@langgenius/dify-ui/button'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { getAgentDetailPath } from '@/features/agent-v2/agent-detail/routes'
import {
getAgentWebAppUrl,
useWebAppAccessPermission,
} from '@/features/agent-v2/agent-detail/web-app-access'
import Link from '@/next/link'
import { consoleQuery } from '@/service/console'
export function AgentPublishSuccess({
agentId,
kind,
onDismiss,
onAccessMethods,
}: {
agentId: string
kind: 'first' | 'update'
onDismiss: () => void
onAccessMethods: () => void
}) {
const { t } = useTranslation('agentV2')
const agentQuery = useQuery(
consoleQuery.agent.byAgentId.get.queryOptions({
input: { params: { agent_id: agentId } },
}),
)
const agent = agentQuery.data
const accessControl = useWebAppAccessPermission(agent)
const webAppUrl =
agentQuery.isSuccess &&
agent?.access_ready &&
agent.enable_site &&
!accessControl.noAccessPermission
? getAgentWebAppUrl(agent)
: undefined
return (
<div className="p-4">
<div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-2 gap-y-1">
<span aria-hidden className="i-ri-checkbox-circle-fill size-5 shrink-0 text-text-success" />
<p className="min-w-0 system-sm-semibold text-text-primary">
{kind === 'first'
? t(($) => $['agentDetail.configure.publishSuccess.firstTitle'])
: t(($) => $['agentDetail.configure.publishSuccess.updateTitle'])}
</p>
<IconButton
size="md"
className="shrink-0"
aria-label={t(($) => $['agentDetail.configure.publishSuccess.dismiss'])}
onClick={onDismiss}
>
<span aria-hidden className="i-ri-close-line size-4" />
</IconButton>
<p className="col-span-2 col-start-2 system-xs-regular text-text-secondary">
{!webAppUrl
? t(($) => $['agentDetail.configure.publishSuccess.accessDescription'])
: kind === 'first'
? t(($) => $['agentDetail.configure.publishSuccess.firstDescription'])
: t(($) => $['agentDetail.configure.publishSuccess.updateDescription'])}
</p>
</div>
<div className="mt-3 flex flex-wrap items-center justify-end gap-2">
<Link
href={getAgentDetailPath(agentId, 'access')}
onClick={onAccessMethods}
className={buttonVariants({ variant: 'secondary' })}
>
{t(($) => $['agentDetail.configure.publishSuccess.accessMethods'])}
</Link>
{webAppUrl && (
<a
href={webAppUrl}
target="_blank"
rel="noreferrer"
onClick={onDismiss}
className={buttonVariants({ variant: 'primary' })}
>
{t(($) => $['agentDetail.configure.publishSuccess.openWebApp'])}
<span aria-hidden className="i-ri-external-link-line size-4 shrink-0" />
</a>
)}
</div>
</div>
)
}
@@ -1,6 +1,9 @@
'use client'
import type { AgentSoulConfig } from '@dify/contracts/api/console/agent/types.gen'
import type {
AgentPublishResponse,
AgentSoulConfig,
} from '@dify/contracts/api/console/agent/types.gen'
import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
import { mutationOptions, useMutation, useQueryClient } from '@tanstack/react-query'
import { debounce } from 'es-toolkit/compat'
@@ -33,6 +36,12 @@ import {
const DRAFT_AUTOSAVE_WAIT = 5000
export type AgentConfigurePublishResult = {
kind: AgentPublishResponse['publication_kind']
// Keep the submitted draft's identity so the bar can detect edits made during publication.
draft: AgentSoulConfigFormState
}
export function useAgentConfigureSync({
agentId,
agentName,
@@ -188,7 +197,7 @@ export function useAgentConfigureSync({
if (publish) {
if (!publishEnabledRef.current || !getCurrentPermissions().canReleaseAndVersion)
return false
await publishAgent({
const result = await publishAgent({
params: {
agent_id: agentId,
},
@@ -209,6 +218,7 @@ export function useAgentConfigureSync({
}),
}),
])
return result
}
return true
@@ -405,13 +415,13 @@ export function useAgentConfigureSync({
}
}, [saveDirtyDraftOnPageClose])
const publishDraft = useCallback(async () => {
const publishDraft = useCallback(async (): Promise<AgentConfigurePublishResult | false> => {
if (
!publishEnabledRef.current ||
!getCurrentPermissions().canReleaseAndVersion ||
publishInFlightRef.current
)
return
return false
const draft = store.get(agentComposerDraftAtom)
const configSnapshot = formStateToAgentSoulConfig({
@@ -421,7 +431,7 @@ export function useAgentConfigureSync({
})
if (!configSnapshot.model?.model_provider || !configSnapshot.model.model) {
toast.error(tCommon(($) => $['modelProvider.selectModel']))
return
return false
}
const toolPublishIssue = getAgentToolPublishIssue(draft.tools, toolProviderCatalog)
@@ -434,7 +444,7 @@ export function useAgentConfigureSync({
? tWorkflow(($) => $['nodes.agent.toolNotInstallTooltip'], { tool: toolName })
: tWorkflow(($) => $['nodes.agent.toolNotAuthorizedTooltip'], { tool: toolName }),
)
return
return false
}
const knowledgeValidation = validateKnowledgeRetrievals(draft.knowledgeRetrievals)
@@ -443,7 +453,7 @@ export function useAgentConfigureSync({
getKnowledgeValidationMessage(knowledgeValidation.firstIssue?.code) ??
tCommon(($) => $['api.actionFailed']),
)
return
return false
}
publishInFlightRef.current = true
@@ -453,14 +463,14 @@ export function useAgentConfigureSync({
configSnapshot,
draftBaseline: draft,
})
if (!published) return
if (!published || published === true) return false
trackEvent('app_published_time', {
action_mode: 'app',
app_id: agentId,
app_name: agentName,
app_mode: 'agent-v2',
})
toast.success(tCommon(($) => $['api.actionSuccess']))
return { kind: published.publication_kind, draft }
} catch (error) {
let errorData: unknown = error
if (error instanceof Response) {
@@ -0,0 +1,35 @@
'use client'
import type { AgentAppDetailWithSite } from '@dify/contracts/api/console/agent/types.gen'
import { useSuspenseQuery } from '@tanstack/react-query'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { AccessMode, isAccessMode } from '@/models/access-control'
import { useGetUserCanAccessApp } from '@/service/access-control/use-app-access-control'
export function useWebAppAccessPermission(agent: AgentAppDetailWithSite | undefined) {
const appId = agent?.backing_app_id ?? undefined
const accessMode = isAccessMode(agent?.access_mode) ? agent.access_mode : undefined
const { data: webAppAuthEnabled } = useSuspenseQuery({
...systemFeaturesQueryOptions(),
select: (systemFeatures) => systemFeatures.webapp_auth.enabled,
})
const { data: userCanAccessApp, refetch: refetchUserCanAccessApp } = useGetUserCanAccessApp({
appId,
enabled: Boolean(webAppAuthEnabled && appId),
})
return {
noAccessPermission:
webAppAuthEnabled && accessMode !== AccessMode.EXTERNAL_MEMBERS && !userCanAccessApp?.result,
refetchUserCanAccessApp,
}
}
export function getAgentWebAppUrl(agent?: AgentAppDetailWithSite) {
const site = agent?.site
const token = site?.access_token ?? site?.code
if (!token) return ''
const baseUrl =
site?.app_base_url || (typeof window === 'undefined' ? '' : window.location.origin)
return `${baseUrl.replace(/\/$/, '')}/agent/${token}`
}
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} إلى {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} سير عمل",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} سير عمل",
"agentDetail.configure.publishSuccess.accessDescription": "اعرض جميع طرق الوصول لإعداد كيفية استخدام وكيلك المنشور.",
"agentDetail.configure.publishSuccess.accessMethods": "عرض جميع طرق الوصول",
"agentDetail.configure.publishSuccess.dismiss": "إغلاق إرشادات النشر",
"agentDetail.configure.publishSuccess.firstDescription": "أصبح وكيلك متاحًا. افتح تطبيق الويب لتجربة النسخة التي سيراها المستخدمون.",
"agentDetail.configure.publishSuccess.firstTitle": "تم النشر بنجاح",
"agentDetail.configure.publishSuccess.openWebApp": "فتح تطبيق الويب",
"agentDetail.configure.publishSuccess.updateDescription": "أصبحت أحدث تغييراتك متاحة. افتح تطبيق الويب للتحقق من تجربة المستخدم.",
"agentDetail.configure.publishSuccess.updateTitle": "تم نشر التغييرات",
"agentDetail.configure.rightPanel.build": "بناء",
"agentDetail.configure.rightPanel.buildTipBody": "يضبط Build الوكيل عبر الدردشة. صف ما تريده وسيملأ الإعدادات على اليسار.",
"agentDetail.configure.rightPanel.learnMore": "معرفة المزيد",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{name}} üçün {{action}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} iş axını",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} iş axını",
"agentDetail.configure.publishSuccess.accessDescription": "Dərc edilmiş agentdən istifadəni qurmaq üçün bütün giriş üsullarına baxın.",
"agentDetail.configure.publishSuccess.accessMethods": "Bütün giriş üsullarına bax",
"agentDetail.configure.publishSuccess.dismiss": "Dərc təlimatını bağla",
"agentDetail.configure.publishSuccess.firstDescription": "Agentiniz istifadəyə hazırdır. İstifadəçilərin görəcəyi versiyanı sınamaq üçün veb tətbiqi açın.",
"agentDetail.configure.publishSuccess.firstTitle": "Uğurla dərc edildi",
"agentDetail.configure.publishSuccess.openWebApp": "Veb tətbiqi aç",
"agentDetail.configure.publishSuccess.updateDescription": "Son dəyişiklikləriniz dərc edilib. İstifadəçi təcrübəsini yoxlamaq üçün veb tətbiqi açın.",
"agentDetail.configure.publishSuccess.updateTitle": "Dəyişikliklər dərc edildi",
"agentDetail.configure.rightPanel.build": "Qurma",
"agentDetail.configure.rightPanel.buildTipBody": "Qurma rejimi söhbət vasitəsilə agenti konfiqurasiya edir. İstədiyinizi təsvir edin, o, soldakı konfiqurasiyanı dolduracaq.",
"agentDetail.configure.rightPanel.learnMore": "Ətraflı öyrən",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} für {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} Workflow",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} Workflows",
"agentDetail.configure.publishSuccess.accessDescription": "Sieh dir alle Zugriffsmöglichkeiten an, um die Nutzung deines veröffentlichten Agenten einzurichten.",
"agentDetail.configure.publishSuccess.accessMethods": "Alle Zugriffsmöglichkeiten",
"agentDetail.configure.publishSuccess.dismiss": "Veröffentlichungshinweis schließen",
"agentDetail.configure.publishSuccess.firstDescription": "Dein Agent ist live. Öffne die Web-App und teste die Version, die deine Nutzer sehen werden.",
"agentDetail.configure.publishSuccess.firstTitle": "Erfolgreich veröffentlicht",
"agentDetail.configure.publishSuccess.openWebApp": "Web-App öffnen",
"agentDetail.configure.publishSuccess.updateDescription": "Deine neuesten Änderungen sind live. Öffne die Web-App, um die Nutzererfahrung zu prüfen.",
"agentDetail.configure.publishSuccess.updateTitle": "Änderungen veröffentlicht",
"agentDetail.configure.rightPanel.build": "Erstellen",
"agentDetail.configure.rightPanel.buildTipBody": "Build richtet den Agenten per Chat ein. Beschreibe, was du möchtest, und die Einrichtung links wird ausgefüllt.",
"agentDetail.configure.rightPanel.learnMore": "Mehr erfahren",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} to {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} workflow",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} workflows",
"agentDetail.configure.publishSuccess.accessDescription": "View all access methods to configure how to use your published agent.",
"agentDetail.configure.publishSuccess.accessMethods": "View all access methods",
"agentDetail.configure.publishSuccess.dismiss": "Dismiss publish guidance",
"agentDetail.configure.publishSuccess.firstDescription": "Your agent is live. Open the Web App to try the version your users will see.",
"agentDetail.configure.publishSuccess.firstTitle": "Published successfully",
"agentDetail.configure.publishSuccess.openWebApp": "Open Web App",
"agentDetail.configure.publishSuccess.updateDescription": "Your latest changes are live. Open the Web App to verify the user experience.",
"agentDetail.configure.publishSuccess.updateTitle": "Changes published",
"agentDetail.configure.rightPanel.build": "Build",
"agentDetail.configure.rightPanel.buildTipBody": "Build sets up the agent by chatting. Describe what you want and it fills in the setup on the left.",
"agentDetail.configure.rightPanel.learnMore": "Learn more",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} en {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} flujo de trabajo",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} flujos de trabajo",
"agentDetail.configure.publishSuccess.accessDescription": "Consulta todas las formas de acceso para configurar cómo usar tu agente publicado.",
"agentDetail.configure.publishSuccess.accessMethods": "Ver todas las formas de acceso",
"agentDetail.configure.publishSuccess.dismiss": "Cerrar guía de publicación",
"agentDetail.configure.publishSuccess.firstDescription": "Tu agente está disponible. Abre la aplicación web para probar la versión que verán tus usuarios.",
"agentDetail.configure.publishSuccess.firstTitle": "Publicado correctamente",
"agentDetail.configure.publishSuccess.openWebApp": "Abrir aplicación web",
"agentDetail.configure.publishSuccess.updateDescription": "Tus últimos cambios están disponibles. Abre la aplicación web para comprobar la experiencia de usuario.",
"agentDetail.configure.publishSuccess.updateTitle": "Cambios publicados",
"agentDetail.configure.rightPanel.build": "Crear",
"agentDetail.configure.rightPanel.buildTipBody": "Build configura el agente mediante chat. Describe lo que quieres y completará la configuración de la izquierda.",
"agentDetail.configure.rightPanel.learnMore": "Más información",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} به {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} گردش کار",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} گردش کار",
"agentDetail.configure.publishSuccess.accessDescription": "برای تنظیم نحوه استفاده از عامل منتشرشده، همه روش‌های دسترسی را مشاهده کنید.",
"agentDetail.configure.publishSuccess.accessMethods": "مشاهده همه روش‌های دسترسی",
"agentDetail.configure.publishSuccess.dismiss": "بستن راهنمای انتشار",
"agentDetail.configure.publishSuccess.firstDescription": "عامل شما در دسترس است. برنامه وب را باز کنید تا نسخه‌ای را که کاربران خواهند دید امتحان کنید.",
"agentDetail.configure.publishSuccess.firstTitle": "با موفقیت منتشر شد",
"agentDetail.configure.publishSuccess.openWebApp": "باز کردن برنامه وب",
"agentDetail.configure.publishSuccess.updateDescription": "آخرین تغییرات شما منتشر شده‌اند. برای بررسی تجربه کاربری، برنامه وب را باز کنید.",
"agentDetail.configure.publishSuccess.updateTitle": "تغییرات منتشر شدند",
"agentDetail.configure.rightPanel.build": "ساخت",
"agentDetail.configure.rightPanel.buildTipBody": "Build عامل را از طریق گفتگو تنظیم می‌کند. آنچه می‌خواهید را توضیح دهید تا تنظیمات سمت چپ تکمیل شود.",
"agentDetail.configure.rightPanel.learnMore": "بیشتر بدانید",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} pour {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} workflow",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} workflows",
"agentDetail.configure.publishSuccess.accessDescription": "Consultez tous les modes d’accès pour configurer l’utilisation de votre agent publié.",
"agentDetail.configure.publishSuccess.accessMethods": "Voir tous les modes d’accès",
"agentDetail.configure.publishSuccess.dismiss": "Fermer le guide de publication",
"agentDetail.configure.publishSuccess.firstDescription": "Votre agent est en ligne. Ouvrez l’application web pour essayer la version que verront vos utilisateurs.",
"agentDetail.configure.publishSuccess.firstTitle": "Publication réussie",
"agentDetail.configure.publishSuccess.openWebApp": "Ouvrir l’application web",
"agentDetail.configure.publishSuccess.updateDescription": "Vos dernières modifications sont en ligne. Ouvrez l’application web pour vérifier l’expérience utilisateur.",
"agentDetail.configure.publishSuccess.updateTitle": "Modifications publiées",
"agentDetail.configure.rightPanel.build": "Créer",
"agentDetail.configure.rightPanel.buildTipBody": "Build configure l’agent par chat. Décrivez ce que vous voulez et il remplit la configuration à gauche.",
"agentDetail.configure.rightPanel.learnMore": "En savoir plus",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{name}} पर {{action}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} वर्कफ़्लो",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} वर्कफ़्लो",
"agentDetail.configure.publishSuccess.accessDescription": "अपने प्रकाशित एजेंट का उपयोग सेट करने के लिए पहुँच के सभी तरीके देखें।",
"agentDetail.configure.publishSuccess.accessMethods": "पहुँच के सभी तरीके देखें",
"agentDetail.configure.publishSuccess.dismiss": "प्रकाशन मार्गदर्शन बंद करें",
"agentDetail.configure.publishSuccess.firstDescription": "आपका एजेंट लाइव है। उपयोगकर्ताओं को दिखने वाले संस्करण को आज़माने के लिए वेब ऐप खोलें।",
"agentDetail.configure.publishSuccess.firstTitle": "सफलतापूर्वक प्रकाशित",
"agentDetail.configure.publishSuccess.openWebApp": "वेब ऐप खोलें",
"agentDetail.configure.publishSuccess.updateDescription": "आपके नवीनतम बदलाव लाइव हैं। उपयोगकर्ता अनुभव की जाँच करने के लिए वेब ऐप खोलें।",
"agentDetail.configure.publishSuccess.updateTitle": "बदलाव प्रकाशित हुए",
"agentDetail.configure.rightPanel.build": "बिल्ड",
"agentDetail.configure.rightPanel.buildTipBody": "Build chat के ज़रिए agent सेट करता है। आप जो चाहते हैं उसे बताएं और यह बाईं ओर की setup भर देता है।",
"agentDetail.configure.rightPanel.learnMore": "और जानें",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} ke {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} alur kerja",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} alur kerja",
"agentDetail.configure.publishSuccess.accessDescription": "Lihat semua metode akses untuk mengatur cara menggunakan agen yang telah dipublikasikan.",
"agentDetail.configure.publishSuccess.accessMethods": "Lihat semua metode akses",
"agentDetail.configure.publishSuccess.dismiss": "Tutup panduan publikasi",
"agentDetail.configure.publishSuccess.firstDescription": "Agen Anda sudah aktif. Buka aplikasi web untuk mencoba versi yang akan dilihat pengguna Anda.",
"agentDetail.configure.publishSuccess.firstTitle": "Berhasil dipublikasikan",
"agentDetail.configure.publishSuccess.openWebApp": "Buka aplikasi web",
"agentDetail.configure.publishSuccess.updateDescription": "Perubahan terbaru Anda sudah aktif. Buka aplikasi web untuk memeriksa pengalaman pengguna.",
"agentDetail.configure.publishSuccess.updateTitle": "Perubahan dipublikasikan",
"agentDetail.configure.rightPanel.build": "Bangun",
"agentDetail.configure.rightPanel.buildTipBody": "Build menyiapkan agent lewat chat. Jelaskan yang Anda inginkan dan pengaturan di sebelah kiri akan terisi.",
"agentDetail.configure.rightPanel.learnMore": "Pelajari selengkapnya",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} in {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} workflow",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} workflow",
"agentDetail.configure.publishSuccess.accessDescription": "Consulta tutti i metodi di accesso per configurare l’utilizzo del tuo agente pubblicato.",
"agentDetail.configure.publishSuccess.accessMethods": "Vedi tutti i metodi di accesso",
"agentDetail.configure.publishSuccess.dismiss": "Chiudi la guida alla pubblicazione",
"agentDetail.configure.publishSuccess.firstDescription": "Il tuo agente è online. Apri l’app web per provare la versione che vedranno i tuoi utenti.",
"agentDetail.configure.publishSuccess.firstTitle": "Pubblicazione riuscita",
"agentDetail.configure.publishSuccess.openWebApp": "Apri app web",
"agentDetail.configure.publishSuccess.updateDescription": "Le ultime modifiche sono online. Apri l’app web per verificare l’esperienza utente.",
"agentDetail.configure.publishSuccess.updateTitle": "Modifiche pubblicate",
"agentDetail.configure.rightPanel.build": "Crea",
"agentDetail.configure.rightPanel.buildTipBody": "Build configura l’agente tramite chat. Descrivi cosa vuoi e compilerà la configurazione a sinistra.",
"agentDetail.configure.rightPanel.learnMore": "Scopri di più",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{name}} に{{action}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} 件のワークフロー",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} 件のワークフロー",
"agentDetail.configure.publishSuccess.accessDescription": "すべての利用方法を確認し、公開したエージェントの利用方法を設定してください。",
"agentDetail.configure.publishSuccess.accessMethods": "すべてのアクセス方法を見る",
"agentDetail.configure.publishSuccess.dismiss": "公開ガイドを閉じる",
"agentDetail.configure.publishSuccess.firstDescription": "Agent が公開されました。Web アプリを開いて、ユーザーに表示されるバージョンを試してみましょう。",
"agentDetail.configure.publishSuccess.firstTitle": "公開しました",
"agentDetail.configure.publishSuccess.openWebApp": "Web アプリを開く",
"agentDetail.configure.publishSuccess.updateDescription": "最新の変更が反映されました。Web アプリを開いて、ユーザーに表示される内容を確認しましょう。",
"agentDetail.configure.publishSuccess.updateTitle": "変更を公開しました",
"agentDetail.configure.rightPanel.build": "ビルド",
"agentDetail.configure.rightPanel.buildTipBody": "Build はチャットを通じてエージェントを設定します。やりたいことを説明すると、左側の設定が入力されます。",
"agentDetail.configure.rightPanel.learnMore": "詳細を見る",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{name}}에 {{action}}",
"agentDetail.configure.publishImpact.workflowCount_one": "워크플로 {{count}}개",
"agentDetail.configure.publishImpact.workflowCount_other": "워크플로 {{count}}개",
"agentDetail.configure.publishSuccess.accessDescription": "모든 사용 방법을 확인하고 게시된 에이전트의 사용 방법을 설정하세요.",
"agentDetail.configure.publishSuccess.accessMethods": "모든 사용 방법 보기",
"agentDetail.configure.publishSuccess.dismiss": "게시 안내 닫기",
"agentDetail.configure.publishSuccess.firstDescription": "에이전트가 게시되었습니다. 웹 앱을 열어 사용자에게 표시될 버전을 체험하세요.",
"agentDetail.configure.publishSuccess.firstTitle": "게시 완료",
"agentDetail.configure.publishSuccess.openWebApp": "웹 앱 열기",
"agentDetail.configure.publishSuccess.updateDescription": "최신 변경 사항이 게시되었습니다. 웹 앱을 열어 사용자 경험을 확인하세요.",
"agentDetail.configure.publishSuccess.updateTitle": "변경 사항 게시 완료",
"agentDetail.configure.rightPanel.build": "빌드",
"agentDetail.configure.rightPanel.buildTipBody": "Build는 채팅으로 에이전트를 설정합니다. 원하는 내용을 설명하면 왼쪽 설정이 채워집니다.",
"agentDetail.configure.rightPanel.learnMore": "자세히 알아보기",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} ໄປຍັງ {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} ເວີກໂຟລ",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} ເວີກໂຟລ",
"agentDetail.configure.publishSuccess.accessDescription": "ເບິ່ງວິທີເຂົ້າເຖິງທັງໝົດເພື່ອຕັ້ງຄ່າການໃຊ້ງານເອເຈນທີ່ເຜີຍແຜ່ແລ້ວ.",
"agentDetail.configure.publishSuccess.accessMethods": "ເບິ່ງວິທີເຂົ້າເຖິງທັງໝົດ",
"agentDetail.configure.publishSuccess.dismiss": "ປິດຄຳແນະນຳການເຜີຍແຜ່",
"agentDetail.configure.publishSuccess.firstDescription": "ເອເຈນຂອງທ່ານພ້ອມໃຊ້ງານແລ້ວ. ເປີດເວັບແອັບເພື່ອລອງໃຊ້ເວີຊັນທີ່ຜູ້ໃຊ້ຈະເຫັນ.",
"agentDetail.configure.publishSuccess.firstTitle": "ເຜີຍແຜ່ສຳເລັດ",
"agentDetail.configure.publishSuccess.openWebApp": "ເປີດເວັບແອັບ",
"agentDetail.configure.publishSuccess.updateDescription": "ການປ່ຽນແປງຫຼ້າສຸດພ້ອມໃຊ້ງານແລ້ວ. ເປີດເວັບແອັບເພື່ອກວດສອບປະສົບການຜູ້ໃຊ້.",
"agentDetail.configure.publishSuccess.updateTitle": "ເຜີຍແຜ່ການປ່ຽນແປງແລ້ວ",
"agentDetail.configure.rightPanel.build": "ສ້າງ (Build)",
"agentDetail.configure.rightPanel.buildTipBody": "Build ຕັ້ງຄ່າຕົວແທນຜ່ານການສົນທະນາ. ອະທິບາຍສິ່ງທີ່ທ່ານຕ້ອງການ ແລ້ວມັນຈະຕື່ມການຕັ້ງຄ່າດ້ານຊ້າຍໃຫ້ເອງ.",
"agentDetail.configure.rightPanel.learnMore": "ຮຽນຮູ້ເພີ່ມເຕີມ",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} naar {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} workflow",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} workflows",
"agentDetail.configure.publishSuccess.accessDescription": "Bekijk alle toegangsmethoden om het gebruik van je gepubliceerde agent in te stellen.",
"agentDetail.configure.publishSuccess.accessMethods": "Alle toegangsmethoden bekijken",
"agentDetail.configure.publishSuccess.dismiss": "Publicatiehulp sluiten",
"agentDetail.configure.publishSuccess.firstDescription": "Je agent is live. Open de webapp om de versie te proberen die je gebruikers zien.",
"agentDetail.configure.publishSuccess.firstTitle": "Publicatie geslaagd",
"agentDetail.configure.publishSuccess.openWebApp": "Webapp openen",
"agentDetail.configure.publishSuccess.updateDescription": "Je laatste wijzigingen zijn live. Open de webapp om de gebruikerservaring te controleren.",
"agentDetail.configure.publishSuccess.updateTitle": "Wijzigingen gepubliceerd",
"agentDetail.configure.rightPanel.build": "Bouwen",
"agentDetail.configure.rightPanel.buildTipBody": "Build stelt de agent in via chat. Beschrijf wat je wilt en de instellingen links worden ingevuld.",
"agentDetail.configure.rightPanel.learnMore": "Meer informatie",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} do {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} przepływie pracy",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} przepływach pracy",
"agentDetail.configure.publishSuccess.accessDescription": "Sprawdź wszystkie metody dostępu, aby skonfigurować korzystanie z opublikowanego agenta.",
"agentDetail.configure.publishSuccess.accessMethods": "Zobacz wszystkie metody dostępu",
"agentDetail.configure.publishSuccess.dismiss": "Zamknij wskazówki publikacji",
"agentDetail.configure.publishSuccess.firstDescription": "Twój agent jest dostępny. Otwórz aplikację internetową, aby wypróbować wersję, którą zobaczą użytkownicy.",
"agentDetail.configure.publishSuccess.firstTitle": "Opublikowano pomyślnie",
"agentDetail.configure.publishSuccess.openWebApp": "Otwórz aplikację internetową",
"agentDetail.configure.publishSuccess.updateDescription": "Najnowsze zmiany są dostępne. Otwórz aplikację internetową, aby sprawdzić działanie z perspektywy użytkownika.",
"agentDetail.configure.publishSuccess.updateTitle": "Opublikowano zmiany",
"agentDetail.configure.rightPanel.build": "Buduj",
"agentDetail.configure.rightPanel.buildTipBody": "Build konfiguruje agenta przez czat. Opisz, czego chcesz, a ustawienia po lewej zostaną uzupełnione.",
"agentDetail.configure.rightPanel.learnMore": "Dowiedz się więcej",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} em {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} workflow",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} workflows",
"agentDetail.configure.publishSuccess.accessDescription": "Veja todas as formas de acesso para configurar como usar seu agente publicado.",
"agentDetail.configure.publishSuccess.accessMethods": "Ver todas as formas de acesso",
"agentDetail.configure.publishSuccess.dismiss": "Fechar orientação de publicação",
"agentDetail.configure.publishSuccess.firstDescription": "Seu agente está no ar. Abra o aplicativo web para experimentar a versão que seus usuários verão.",
"agentDetail.configure.publishSuccess.firstTitle": "Publicado com sucesso",
"agentDetail.configure.publishSuccess.openWebApp": "Abrir aplicativo web",
"agentDetail.configure.publishSuccess.updateDescription": "Suas últimas alterações estão no ar. Abra o aplicativo web para verificar a experiência do usuário.",
"agentDetail.configure.publishSuccess.updateTitle": "Alterações publicadas",
"agentDetail.configure.rightPanel.build": "Criar",
"agentDetail.configure.rightPanel.buildTipBody": "Build configura o agente por chat. Descreva o que você quer e ele preenche a configuração à esquerda.",
"agentDetail.configure.rightPanel.learnMore": "Saiba mais",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} pentru {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} workflow",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} workflow-uri",
"agentDetail.configure.publishSuccess.accessDescription": "Vezi toate metodele de acces pentru a configura utilizarea agentului publicat.",
"agentDetail.configure.publishSuccess.accessMethods": "Vezi toate metodele de acces",
"agentDetail.configure.publishSuccess.dismiss": "Închide ghidul de publicare",
"agentDetail.configure.publishSuccess.firstDescription": "Agentul tău este disponibil. Deschide aplicația web pentru a încerca versiunea pe care o vor vedea utilizatorii.",
"agentDetail.configure.publishSuccess.firstTitle": "Publicat cu succes",
"agentDetail.configure.publishSuccess.openWebApp": "Deschide aplicația web",
"agentDetail.configure.publishSuccess.updateDescription": "Ultimele modificări sunt disponibile. Deschide aplicația web pentru a verifica experiența utilizatorilor.",
"agentDetail.configure.publishSuccess.updateTitle": "Modificări publicate",
"agentDetail.configure.rightPanel.build": "Construire",
"agentDetail.configure.rightPanel.buildTipBody": "Build configurează agentul prin chat. Descrie ce dorești, iar configurarea din stânga va fi completată.",
"agentDetail.configure.rightPanel.learnMore": "Află mai multe",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} в {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} рабочем процессе",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} рабочих процессах",
"agentDetail.configure.publishSuccess.accessDescription": "Просмотрите все способы доступа, чтобы настроить использование опубликованного агента.",
"agentDetail.configure.publishSuccess.accessMethods": "Все способы доступа",
"agentDetail.configure.publishSuccess.dismiss": "Закрыть подсказку о публикации",
"agentDetail.configure.publishSuccess.firstDescription": "Ваш агент доступен. Откройте веб-приложение и попробуйте версию, которую увидят пользователи.",
"agentDetail.configure.publishSuccess.firstTitle": "Успешно опубликовано",
"agentDetail.configure.publishSuccess.openWebApp": "Открыть веб-приложение",
"agentDetail.configure.publishSuccess.updateDescription": "Последние изменения опубликованы. Откройте веб-приложение, чтобы проверить работу со стороны пользователя.",
"agentDetail.configure.publishSuccess.updateTitle": "Изменения опубликованы",
"agentDetail.configure.rightPanel.build": "Сборка",
"agentDetail.configure.rightPanel.buildTipBody": "Build настраивает агента через чат. Опишите, что вам нужно, и он заполнит настройки слева.",
"agentDetail.configure.rightPanel.learnMore": "Подробнее",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} v {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} poteku dela",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} potekih dela",
"agentDetail.configure.publishSuccess.accessDescription": "Oglejte si vse načine dostopa in nastavite uporabo objavljenega agenta.",
"agentDetail.configure.publishSuccess.accessMethods": "Ogled vseh načinov dostopa",
"agentDetail.configure.publishSuccess.dismiss": "Zapri navodila za objavo",
"agentDetail.configure.publishSuccess.firstDescription": "Vaš agent je na voljo. Odprite spletno aplikacijo in preizkusite različico, ki jo bodo videli uporabniki.",
"agentDetail.configure.publishSuccess.firstTitle": "Uspešno objavljeno",
"agentDetail.configure.publishSuccess.openWebApp": "Odpri spletno aplikacijo",
"agentDetail.configure.publishSuccess.updateDescription": "Vaše najnovejše spremembe so na voljo. Odprite spletno aplikacijo in preverite uporabniško izkušnjo.",
"agentDetail.configure.publishSuccess.updateTitle": "Spremembe objavljene",
"agentDetail.configure.rightPanel.build": "Izdelaj",
"agentDetail.configure.rightPanel.buildTipBody": "Build nastavi agenta prek klepeta. Opišite, kaj želite, in izpolnil bo nastavitev na levi.",
"agentDetail.configure.rightPanel.learnMore": "Več o tem",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}}ไปยัง {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "เวิร์กโฟลว์ {{count}} รายการ",
"agentDetail.configure.publishImpact.workflowCount_other": "เวิร์กโฟลว์ {{count}} รายการ",
"agentDetail.configure.publishSuccess.accessDescription": "ดูวิธีเข้าถึงทั้งหมดเพื่อตั้งค่าการใช้งานเอเจนต์ที่เผยแพร่แล้ว",
"agentDetail.configure.publishSuccess.accessMethods": "ดูวิธีเข้าถึงทั้งหมด",
"agentDetail.configure.publishSuccess.dismiss": "ปิดคำแนะนำการเผยแพร่",
"agentDetail.configure.publishSuccess.firstDescription": "เอเจนต์ของคุณพร้อมใช้งานแล้ว เปิดเว็บแอปเพื่อลองใช้เวอร์ชันที่ผู้ใช้จะเห็น",
"agentDetail.configure.publishSuccess.firstTitle": "เผยแพร่สำเร็จ",
"agentDetail.configure.publishSuccess.openWebApp": "เปิดเว็บแอป",
"agentDetail.configure.publishSuccess.updateDescription": "การเปลี่ยนแปลงล่าสุดพร้อมใช้งานแล้ว เปิดเว็บแอปเพื่อตรวจสอบประสบการณ์ของผู้ใช้",
"agentDetail.configure.publishSuccess.updateTitle": "เผยแพร่การเปลี่ยนแปลงแล้ว",
"agentDetail.configure.rightPanel.build": "สร้าง",
"agentDetail.configure.rightPanel.buildTipBody": "Build ตั้งค่าเอเจนต์ผ่านการแชท อธิบายสิ่งที่คุณต้องการ แล้วระบบจะเติมการตั้งค่าทางซ้ายให้",
"agentDetail.configure.rightPanel.learnMore": "เรียนรู้เพิ่มเติม",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{name}} için {{action}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} iş akışı",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} iş akışı",
"agentDetail.configure.publishSuccess.accessDescription": "Yayınlanan ajanınızı nasıl kullanacağınızı yapılandırmak için tüm erişim yöntemlerini görüntüleyin.",
"agentDetail.configure.publishSuccess.accessMethods": "Tüm erişim yöntemlerini görüntüle",
"agentDetail.configure.publishSuccess.dismiss": "Yayınlama rehberini kapat",
"agentDetail.configure.publishSuccess.firstDescription": "Ajanınız yayında. Kullanıcılarınızın göreceği sürümü denemek için web uygulamasını açın.",
"agentDetail.configure.publishSuccess.firstTitle": "Başarıyla yayınlandı",
"agentDetail.configure.publishSuccess.openWebApp": "Web uygulamasını aç",
"agentDetail.configure.publishSuccess.updateDescription": "Son değişiklikleriniz yayında. Kullanıcı deneyimini doğrulamak için web uygulamasını açın.",
"agentDetail.configure.publishSuccess.updateTitle": "Değişiklikler yayınlandı",
"agentDetail.configure.rightPanel.build": "Oluştur",
"agentDetail.configure.rightPanel.buildTipBody": "Build, ajanı sohbet yoluyla ayarlar. Ne istediğinizi açıklayın; soldaki kurulum alanlarını doldurur.",
"agentDetail.configure.rightPanel.learnMore": "Daha fazla bilgi",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} до {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} робочому процесі",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} робочих процесах",
"agentDetail.configure.publishSuccess.accessDescription": "Перегляньте всі способи доступу, щоб налаштувати використання опублікованого агента.",
"agentDetail.configure.publishSuccess.accessMethods": "Усі способи доступу",
"agentDetail.configure.publishSuccess.dismiss": "Закрити підказку про публікацію",
"agentDetail.configure.publishSuccess.firstDescription": "Ваш агент доступний. Відкрийте вебзастосунок і спробуйте версію, яку побачать користувачі.",
"agentDetail.configure.publishSuccess.firstTitle": "Успішно опубліковано",
"agentDetail.configure.publishSuccess.openWebApp": "Відкрити вебзастосунок",
"agentDetail.configure.publishSuccess.updateDescription": "Останні зміни опубліковано. Відкрийте вебзастосунок, щоб перевірити роботу з погляду користувача.",
"agentDetail.configure.publishSuccess.updateTitle": "Зміни опубліковано",
"agentDetail.configure.rightPanel.build": "Створення",
"agentDetail.configure.rightPanel.buildTipBody": "Build налаштовує агента через чат. Опишіть, що потрібно, і він заповнить налаштування ліворуч.",
"agentDetail.configure.rightPanel.learnMore": "Дізнатися більше",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}} đến {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} quy trình làm việc",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} quy trình làm việc",
"agentDetail.configure.publishSuccess.accessDescription": "Xem tất cả cách truy cập để thiết lập cách sử dụng agent đã xuất bản.",
"agentDetail.configure.publishSuccess.accessMethods": "Xem tất cả cách truy cập",
"agentDetail.configure.publishSuccess.dismiss": "Đóng hướng dẫn xuất bản",
"agentDetail.configure.publishSuccess.firstDescription": "Agent của bạn đã hoạt động. Mở ứng dụng web để thử phiên bản mà người dùng sẽ thấy.",
"agentDetail.configure.publishSuccess.firstTitle": "Xuất bản thành công",
"agentDetail.configure.publishSuccess.openWebApp": "Mở ứng dụng web",
"agentDetail.configure.publishSuccess.updateDescription": "Các thay đổi mới nhất đã có hiệu lực. Mở ứng dụng web để kiểm tra trải nghiệm người dùng.",
"agentDetail.configure.publishSuccess.updateTitle": "Đã xuất bản thay đổi",
"agentDetail.configure.rightPanel.build": "Xây dựng",
"agentDetail.configure.rightPanel.buildTipBody": "Build thiết lập agent bằng cách trò chuyện. Mô tả điều bạn muốn và phần thiết lập bên trái sẽ được điền.",
"agentDetail.configure.rightPanel.learnMore": "Tìm hiểu thêm",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}}到 {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} 个工作流",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} 个工作流",
"agentDetail.configure.publishSuccess.accessDescription": "查看所有使用方式,配置如何使用已发布的 Agent。",
"agentDetail.configure.publishSuccess.accessMethods": "查看所有访问方式",
"agentDetail.configure.publishSuccess.dismiss": "关闭发布引导",
"agentDetail.configure.publishSuccess.firstDescription": "Agent 已上线。打开 Web 应用,尝试用户会看到的应用版本。",
"agentDetail.configure.publishSuccess.firstTitle": "发布成功",
"agentDetail.configure.publishSuccess.openWebApp": "打开 Web App",
"agentDetail.configure.publishSuccess.updateDescription": "最新更改已生效。打开 Web 应用,检查用户体验是否符合预期。",
"agentDetail.configure.publishSuccess.updateTitle": "更改已发布",
"agentDetail.configure.rightPanel.build": "构建",
"agentDetail.configure.rightPanel.buildTipBody": "描述你的目标,指示 Agent 安装并认证工具,或带它完成一个完整示例。保存后,你的指令将被固化到 Agent 中。",
"agentDetail.configure.rightPanel.learnMore": "了解更多",
+8
View File
@@ -186,6 +186,14 @@
"agentDetail.configure.publishImpact.title": "{{action}}到 {{name}}",
"agentDetail.configure.publishImpact.workflowCount_one": "{{count}} 個工作流程",
"agentDetail.configure.publishImpact.workflowCount_other": "{{count}} 個工作流程",
"agentDetail.configure.publishSuccess.accessDescription": "查看所有使用方式,設定如何使用已發佈的 Agent。",
"agentDetail.configure.publishSuccess.accessMethods": "查看所有使用方式",
"agentDetail.configure.publishSuccess.dismiss": "關閉發佈引導",
"agentDetail.configure.publishSuccess.firstDescription": "你的 Agent 已上線。開啟 Web App,體驗使用者將看到的版本。",
"agentDetail.configure.publishSuccess.firstTitle": "發佈成功",
"agentDetail.configure.publishSuccess.openWebApp": "開啟 Web App",
"agentDetail.configure.publishSuccess.updateDescription": "你的最新變更已上線。開啟 Web App,驗證使用者體驗。",
"agentDetail.configure.publishSuccess.updateTitle": "變更已發佈",
"agentDetail.configure.rightPanel.build": "建置",
"agentDetail.configure.rightPanel.buildTipBody": "描述你的目標,指示 Agent 安裝並認證工具,或帶它完成一個完整範例。儲存後,你的指令將被固定到 Agent 中。",
"agentDetail.configure.rightPanel.learnMore": "了解更多",
+1
View File
@@ -137,6 +137,7 @@ const createComposerState = (
const createAgentPublishResponse = (
overrides: Partial<AgentPublishMutationResponse> = {},
): AgentPublishMutationResponse => ({
publication_kind: 'update',
active_config_snapshot: {
id: 'snapshot-1',
version: 1,
+1 -1
View File
@@ -10,7 +10,7 @@ import { nextStaticImageTestPlugin } from './plugins/vite/next-static-image-test
const projectRoot = fileURLToPath(new URL('.', import.meta.url))
const isCI = !!process.env.CI
const rootClientInjectTarget = getRootClientInjectTarget(projectRoot)
const browserTestPattern = 'app/**/*.browser.spec.{ts,tsx}'
const browserTestPattern = '{app,features}/**/*.browser.spec.{ts,tsx}'
export default defineConfig(({ command, mode, isPreview }) => {
const isTest = mode === 'test'