feat(workflow-generator): enhance the AI auto-creation flow end-to-end (#38175)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Crazywoola
2026-07-01 02:28:58 +00:00
committed by GitHub
co-authored by Claude Opus 4.8 autofix-ci[bot] Copilot
parent 3ad06bebd9
commit 8809cc036d
85 changed files with 3386 additions and 500 deletions
@@ -5,6 +5,7 @@ import { createCommand } from '../create'
vi.mock('@remixicon/react', () => ({
RiChat3Line: () => null,
RiNodeTree: () => null,
RiSparkling2Line: () => null,
}))
// search() localises its labels via getI18n(); echo the key back so the
@@ -50,11 +51,11 @@ describe('/create slash command', () => {
})
describe('search()', () => {
// An empty arg list should surface every option; the submenu uses this to
// render its initial list when the user types just `/create`.
it('should surface both workflow and chatflow when args is empty', async () => {
// An empty arg list should surface every option (Auto first); the submenu
// uses this to render its initial list when the user types just `/create`.
it('should surface auto, workflow and chatflow when args is empty', async () => {
const results = await createCommand.search('')
expect(results.map(r => r.id)).toEqual(['create-workflow', 'create-chatflow'])
expect(results.map(r => r.id)).toEqual(['create-auto', 'create-workflow', 'create-chatflow'])
})
// Typing a partial keyword should narrow the list and each result should
@@ -63,13 +64,20 @@ describe('/create slash command', () => {
const results = await createCommand.search('chat')
expect(results.map(r => r.id)).toEqual(['create-chatflow'])
expect(results[0]!.data.command).toBe('create.open')
expect(results[0]!.data.args).toEqual({ mode: 'advanced-chat' })
expect(results[0]!.data.args).toEqual({ mode: 'advanced-chat', auto: false, instruction: '' })
})
// A non-matching query returns an empty list rather than throwing, so the
// goto-anything dialog can render an empty-state without special-casing.
it('should return an empty list when the query matches nothing', async () => {
const results = await createCommand.search('zzz-no-match')
// The Auto option carries the auto flag so the modal opens in auto-mode.
it('should flag the auto option so the planner picks the app type', async () => {
const results = await createCommand.search('')
expect(results[0]!.id).toBe('create-auto')
expect(results[0]!.data.args).toEqual({ mode: 'advanced-chat', auto: true, instruction: '' })
})
// A non-matching single-token query returns an empty list rather than
// throwing, so the goto-anything dialog can render an empty-state.
it('should return an empty list when a single-token query matches nothing', async () => {
const results = await createCommand.search('zzz')
expect(results).toEqual([])
})
@@ -77,25 +85,40 @@ describe('/create slash command', () => {
// than hardcoded English, so the palette renders in the user's language.
it('should source titles and descriptions from i18n keys', async () => {
const results = await createCommand.search('')
expect(results[0]!.title).toBe('gotoAnything.actions.createWorkflow')
expect(results[0]!.description).toBe('gotoAnything.actions.createWorkflowDesc')
expect(results[1]!.title).toBe('gotoAnything.actions.createChatflow')
expect(results[1]!.description).toBe('gotoAnything.actions.createChatflowDesc')
expect(results[1]!.title).toBe('gotoAnything.actions.createWorkflow')
expect(results[1]!.description).toBe('gotoAnything.actions.createWorkflowDesc')
expect(results[2]!.title).toBe('gotoAnything.actions.createChatflow')
expect(results[2]!.description).toBe('gotoAnything.actions.createChatflowDesc')
})
// The localised label is also searchable, not just the id — a token that
// appears only in the (mocked) title key still narrows the list, proving
// the filter consults the translated label.
// appears only in the (mocked) title key still narrows the list.
it('should filter by the localised label, not just the id', async () => {
const results = await createCommand.search('createChatflow')
expect(results.map(r => r.id)).toEqual(['create-chatflow'])
})
// Inline capture: a leading mode word selects that option and the rest of
// the text becomes the pre-filled instruction surfaced as the description.
it('should capture a trailing instruction when the first word names a mode', async () => {
const results = await createCommand.search('workflow summarize a URL')
expect(results.map(r => r.id)).toEqual(['create-workflow'])
expect(results[0]!.data.args).toEqual({ mode: 'workflow', auto: false, instruction: 'summarize a URL' })
expect(results[0]!.description).toBe('summarize a URL')
})
// Inline capture without a leading mode word keeps every option, each
// pre-filled with the full text so the user just picks the type.
it('should keep all options with the full text when no leading mode word', async () => {
const results = await createCommand.search('summarize a URL')
expect(results.map(r => r.id)).toEqual(['create-auto', 'create-workflow', 'create-chatflow'])
results.forEach((r) => {
expect((r.data.args as { instruction: string }).instruction).toBe('summarize a URL')
})
})
})
describe('register() — `create.open` command-bus handler', () => {
// Register populates the global command bus; tests below rely on it so we
// run it once per case and clean up via the symmetric unregister(). Reset
// the app-store state so each case controls its own Studio context.
beforeEach(() => {
mockAppStore.appDetail = undefined
createCommand.register?.({} as never)
@@ -105,17 +128,32 @@ describe('/create slash command', () => {
createCommand.unregister?.()
})
// No Studio app open (e.g. /create from the apps list) — the modal opens
// for new-app creation only, with just the requested mode.
it('should open the generator with only the requested mode when no Studio app is open', async () => {
// No Studio app open — the modal opens for new-app creation only.
it('should open the generator with the requested mode when no Studio app is open', async () => {
await executeCommand('create.open', { mode: 'workflow' })
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'workflow' })
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'workflow', autoMode: false, initialInstruction: '' })
})
// In-Studio create-and-apply: when a graph-based app is open and its mode
// matches the picked mode, the handler threads id + mode through so the
// modal can offer "Apply to current draft".
// Inline-captured instruction threads through to the modal.
it('should thread the captured instruction through to the generator', async () => {
await executeCommand('create.open', { mode: 'workflow', instruction: 'summarize a URL' })
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'workflow', autoMode: false, initialInstruction: 'summarize a URL' })
})
// Auto-mode always creates a new app, even with a matching Studio open,
// because the planner may resolve a different type than the open canvas.
it('should open new-app auto-mode even when a matching Studio app is open', async () => {
mockAppStore.appDetail = { id: 'abc-123', mode: 'advanced-chat' }
await executeCommand('create.open', { mode: 'advanced-chat', auto: true })
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'advanced-chat', autoMode: true, initialInstruction: '' })
})
// In-Studio create-and-apply: a matching graph-based app threads id + mode
// through so the modal can offer "Apply to current draft".
it('should thread the current app context when a matching Studio app is open', async () => {
mockAppStore.appDetail = { id: 'abc-123', mode: 'workflow' }
@@ -125,37 +163,35 @@ describe('/create slash command', () => {
mode: 'workflow',
currentAppId: 'abc-123',
currentAppMode: 'workflow',
initialInstruction: '',
})
})
// Mode mismatch (Workflow Studio open, but the user picked Chatflow) must
// NOT capture currentAppId — applying a chatflow graph onto a workflow
// draft is the dead-end we explicitly avoid, so it stays new-app only.
// Mode mismatch must NOT capture currentAppId — applying a chatflow graph
// onto a workflow draft is the dead-end we explicitly avoid.
it('should fall back to new-app only when the picked mode differs from the open app', async () => {
mockAppStore.appDetail = { id: 'abc-123', mode: 'workflow' }
await executeCommand('create.open', { mode: 'advanced-chat' })
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'advanced-chat' })
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'advanced-chat', autoMode: false, initialInstruction: '' })
})
// Non-graph Studio apps (Chat / Agent / Completion) have no canvas to
// apply onto, so the handler ignores them and opens new-app only.
// Non-graph Studio apps (Chat / Agent / Completion) have no canvas to apply
// onto, so the handler ignores them and opens new-app only.
it('should ignore non-graph app modes and open new-app only', async () => {
mockAppStore.appDetail = { id: 'abc-123', mode: 'chat' }
await executeCommand('create.open', { mode: 'workflow' })
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'workflow' })
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'workflow', autoMode: false, initialInstruction: '' })
})
// Defensive fallback: if a caller forgets to pass a mode (or passes none),
// the handler must still open the generator with a safe default rather
// than crashing the goto-anything dialog.
// Defensive fallback: a missing mode still opens the generator safely.
it('should default to workflow mode when no args are passed', async () => {
await executeCommand('create.open')
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'workflow' })
expect(mockOpenGenerator).toHaveBeenCalledWith({ mode: 'workflow', autoMode: false, initialInstruction: '' })
})
})
@@ -1,6 +1,6 @@
import type { SlashCommandHandler } from './types'
import type { WorkflowGeneratorMode } from '@/app/components/workflow/workflow-generator/types'
import { RiChat3Line, RiNodeTree } from '@remixicon/react'
import { RiChat3Line, RiNodeTree, RiSparkling2Line } from '@remixicon/react'
import * as React from 'react'
import { getI18n } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
@@ -14,18 +14,30 @@ type CreateOption = {
titleKey: string
/** i18n key (ns: 'app') for the option's one-line description. */
descKey: string
/** Provisional concrete mode for suggestions/preview; `auto` resolves the real one server-side. */
mode: WorkflowGeneratorMode
/** When set, the modal opens in auto-mode and the planner picks the app type. */
auto?: boolean
icon: React.ComponentType<{ className?: string }>
}
// `as const` keeps titleKey/descKey as literal types so the typed `i18n.t`
// accepts them as known keys; `satisfies` still validates the shape.
const OPTIONS = [
{
id: 'auto',
titleKey: 'gotoAnything.actions.createAuto',
descKey: 'gotoAnything.actions.createAutoDesc',
mode: 'advanced-chat',
auto: true,
icon: RiSparkling2Line,
},
{
id: 'workflow',
titleKey: 'gotoAnything.actions.createWorkflow',
descKey: 'gotoAnything.actions.createWorkflowDesc',
mode: 'workflow',
auto: false,
icon: RiNodeTree,
},
{
@@ -33,6 +45,7 @@ const OPTIONS = [
titleKey: 'gotoAnything.actions.createChatflow',
descKey: 'gotoAnything.actions.createChatflowDesc',
mode: 'advanced-chat',
auto: false,
icon: RiChat3Line,
},
] as const satisfies readonly CreateOption[]
@@ -43,14 +56,17 @@ const OPTIONS = [
*
* The user-picked mode is passed through to the generator modal explicitly
* rather than sniffed from the URL, which avoids the mode-mismatch dead-end
* the URL-sniffing approach used to produce.
* the URL-sniffing approach used to produce. An `Auto` option lets the planner
* pick the app type from the description.
*
* Inline capture: a multi-word query threads the trailing text into the modal as
* a pre-filled instruction (e.g. `/create workflow summarize a URL`, or
* `/create translate this` to pre-fill while still picking the type).
*
* When triggered from inside a graph-based Studio (Workflow / Advanced-Chat)
* whose app mode matches the picked mode, it threads the current app (id +
* mode) through so the modal offers "Apply to current draft" — this is the
* in-Studio create-and-apply journey that replaced the old toolbar button.
* With no Studio app open, or when the picked mode differs from the open
* app's mode, it falls back to new-app creation only.
* whose app mode matches the picked mode, it threads the current app (id + mode)
* through so the modal offers "Apply to current draft". Auto-mode always creates
* a new app since the planner may pick a different type than the open Studio.
*/
export const createCommand: SlashCommandHandler = {
name: 'create',
@@ -64,33 +80,60 @@ export const createCommand: SlashCommandHandler = {
const i18n = getI18n()
const tr = (key: (typeof OPTIONS)[number]['titleKey' | 'descKey']) =>
i18n.t(key, { ns: 'app', lng: locale })
const query = args.trim().toLowerCase()
const filtered = OPTIONS.filter(
opt => !query || opt.id.includes(query) || tr(opt.titleKey).toLowerCase().includes(query),
const renderIcon = (Icon: CreateOption['icon']) => (
<div className="flex h-6 w-6 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-components-panel-bg">
<Icon className="size-4 text-text-tertiary" />
</div>
)
return filtered.map(opt => ({
const toResult = (opt: (typeof OPTIONS)[number], instruction: string) => ({
id: `create-${opt.id}`,
title: tr(opt.titleKey),
description: tr(opt.descKey),
// Surface the captured instruction so the user sees it was picked up; fall
// back to the option's static description otherwise.
description: instruction || tr(opt.descKey),
type: 'command' as const,
icon: (
<div className="flex h-6 w-6 items-center justify-center rounded-md border-[0.5px] border-divider-regular bg-components-panel-bg">
<opt.icon className="size-4 text-text-tertiary" />
</div>
),
data: { command: 'create.open', args: { mode: opt.mode } },
}))
icon: renderIcon(opt.icon),
data: { command: 'create.open', args: { mode: opt.mode, auto: !!opt.auto, instruction } },
})
const trimmed = args.trim()
const tokens = trimmed ? trimmed.split(/\s+/) : []
// Single token (or empty): narrow the option list by name — the original
// submenu-filter behaviour, no instruction captured.
if (tokens.length <= 1) {
const query = trimmed.toLowerCase()
return OPTIONS
.filter(opt => !query || opt.id.includes(query) || tr(opt.titleKey).toLowerCase().includes(query))
.map(opt => toResult(opt, ''))
}
// Multi-token: inline capture. If the first word names a mode, use it and
// treat the rest as the instruction; otherwise keep every option with the
// full text as the instruction so the user just picks the type.
const first = tokens[0]!.toLowerCase()
const matched = OPTIONS.find(opt => opt.id === first || tr(opt.titleKey).toLowerCase() === first)
if (matched)
return [toResult(matched, tokens.slice(1).join(' '))]
return OPTIONS.map(opt => toResult(opt, trimmed))
},
register() {
registerCommands({
'create.open': async (args) => {
const mode: WorkflowGeneratorMode = (args?.mode ?? 'workflow') as WorkflowGeneratorMode
const autoMode = !!args?.auto
const initialInstruction = typeof args?.instruction === 'string' ? args.instruction : ''
// If a graph-based Studio app is open and its mode matches the picked
// mode, thread it through so the modal can offer "Apply to current
// draft". A mode mismatch (or no app open) falls back to new-app only,
// mirroring the precondition the modal uses for canApplyToCurrent.
// Auto-mode always creates a new app — the planner may resolve a type
// different from the open Studio, so applying to the current draft is
// unsafe.
const appDetail = useAppStore.getState().appDetail
const currentAppMode: WorkflowGeneratorMode | null
= appDetail?.mode === AppModeEnum.WORKFLOW
@@ -99,16 +142,17 @@ export const createCommand: SlashCommandHandler = {
? 'advanced-chat'
: null
if (appDetail && currentAppMode === mode) {
if (!autoMode && appDetail && currentAppMode === mode) {
useWorkflowGeneratorStore.getState().openGenerator({
mode,
currentAppId: appDetail.id,
currentAppMode,
initialInstruction,
})
return
}
useWorkflowGeneratorStore.getState().openGenerator({ mode })
useWorkflowGeneratorStore.getState().openGenerator({ mode, autoMode, initialInstruction })
},
})
},
@@ -262,4 +262,26 @@ describe('applyToCurrentApp', () => {
.rejects
.toBe(original)
})
it('should NOT translate string or null sync rejections', async () => {
mockFetchWorkflowDraft.mockResolvedValue({
hash: 'h1',
features: {},
environment_variables: [],
conversation_variables: [],
})
// String error
const strError = 'some string error'
mockSyncWorkflowDraft.mockRejectedValueOnce(strError)
await expect(applyToCurrentApp({ appId: 'app-9', graph: makeGraph() }))
.rejects
.toBe(strError)
// Null error
mockSyncWorkflowDraft.mockRejectedValueOnce(null)
await expect(applyToCurrentApp({ appId: 'app-9', graph: makeGraph() }))
.rejects
.toBeNull()
})
})
@@ -1,57 +1,168 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { fetchWorkflowInstructionSuggestions } from '@/service/debug'
import ExamplePrompts from '../example-prompts'
vi.mock('@/service/debug', () => ({
fetchWorkflowInstructionSuggestions: vi.fn(),
}))
// ahooks' useSessionStorageState keeps an in-memory cache that survives
// sessionStorage.clear(), leaking suggestions across tests. Swap it for a plain
// useState so each mount starts cold and the mount-time fetch is deterministic.
vi.mock('ahooks', async (importOriginal) => {
const actual = await importOriginal<typeof import('ahooks')>()
const React = await import('react')
return {
...actual,
useSessionStorageState: <T,>(key: string, options?: { defaultValue?: T }) => {
const stored = sessionStorage.getItem(key)
const initial = stored ? JSON.parse(stored) : options?.defaultValue
return React.useState<T | undefined>(initial)
},
}
})
const mockFetch = vi.mocked(fetchWorkflowInstructionSuggestions)
describe('ExamplePrompts', () => {
beforeEach(() => {
vi.clearAllMocks()
// Suggestions are session-cached per mode — clear so each test starts cold
// and the mount-time fetch fires deterministically.
sessionStorage.clear()
// Safe default (empty → static fallback); tests override as needed. Avoids
// any leftover mock implementation bleeding across tests.
mockFetch.mockResolvedValue({ suggestions: [] })
})
describe('rendering', () => {
// Workflow mode surfaces a curated 4-prompt set; the count matters
// because the chip row's wrap behaviour was tuned for ≤ 4 entries.
it('should render the 4 workflow-mode prompts', () => {
describe('AI suggestions', () => {
// The primary content is AI-generated, workspace-grounded chips fetched on
// open; they replace the static list once they arrive.
it('should render AI-generated chips when the backend returns suggestions', async () => {
mockFetch.mockResolvedValue({ suggestions: ['Build a triage bot', 'Summarize PDFs'] })
render(<ExamplePrompts mode="workflow" onSelect={vi.fn()} />)
expect(screen.getAllByRole('button')).toHaveLength(4)
expect(screen.getByRole('button', { name: /workflowGenerator\.examples\.workflow\.summarize/i })).toBeInTheDocument()
expect(await screen.findByRole('button', { name: 'Build a triage bot' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Summarize PDFs' })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: /workflowGenerator\.examples\.workflow\.summarize/i })).not.toBeInTheDocument()
})
// Empty generation (no default model / quota) must silently fall back to the
// curated static list so the row is never blank.
it('should fall back to the static workflow list when generation returns nothing', async () => {
mockFetch.mockResolvedValue({ suggestions: [] })
render(<ExamplePrompts mode="workflow" onSelect={vi.fn()} />)
expect(await screen.findByRole('button', { name: /workflowGenerator\.examples\.workflow\.summarize/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /workflowGenerator\.examples\.workflow\.translate/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /workflowGenerator\.examples\.workflow\.rag/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /workflowGenerator\.examples\.workflow\.classify/i })).toBeInTheDocument()
})
// Advanced-chat mode surfaces a different (3-prompt) set tailored to
// chatflow patterns. None of the workflow prompts should leak through.
it('should render the 3 chatflow-mode prompts when mode is advanced-chat', () => {
// Advanced-chat mode falls back to a different curated set with no workflow leakage.
it('should fall back to the static chatflow list for advanced-chat mode', async () => {
mockFetch.mockResolvedValue({ suggestions: [] })
render(<ExamplePrompts mode="advanced-chat" onSelect={vi.fn()} />)
expect(screen.getAllByRole('button')).toHaveLength(3)
expect(screen.getByRole('button', { name: /workflowGenerator\.examples\.chatflow\.support/i })).toBeInTheDocument()
expect(await screen.findByRole('button', { name: /workflowGenerator\.examples\.chatflow\.support/i })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: /workflowGenerator\.examples\.workflow\.summarize/i })).not.toBeInTheDocument()
})
// The "Try one of these" label anchors the row visually; missing it
// would degrade the section to anonymous chips.
it('should render a section label above the chips', () => {
// A failed request must not toast or blow up — it degrades to the static list.
it('should silently fall back to the static list when generation throws', async () => {
mockFetch.mockRejectedValue(new Error('boom'))
render(<ExamplePrompts mode="workflow" onSelect={vi.fn()} />)
expect(screen.getByText(/workflowGenerator\.examples\.label/i)).toBeInTheDocument()
expect(await screen.findByRole('button', { name: /workflowGenerator\.examples\.workflow\.summarize/i })).toBeInTheDocument()
})
it('should silently ignore AbortError when unmounted or refreshed', async () => {
// Simulate fetch that we can manually abort
mockFetch.mockImplementation(async (_, opts) => {
return new Promise((resolve, reject) => {
if (opts?.getAbortController) {
const controller = new AbortController()
opts.getAbortController(controller)
controller.signal.addEventListener('abort', () => {
const err = new Error('AbortError')
err.name = 'AbortError'
reject(err)
})
}
})
})
const { unmount } = render(<ExamplePrompts mode="workflow" onSelect={vi.fn()} />)
// Unmount triggers abort
unmount()
expect(mockFetch).toHaveBeenCalledTimes(1)
})
it('should not fetch on mount if suggestions are already cached', async () => {
// Simulate already having cached suggestions
sessionStorage.setItem('workflow-gen-suggestions-workflow', JSON.stringify(['cached suggestion']))
render(<ExamplePrompts mode="workflow" onSelect={vi.fn()} />)
expect(await screen.findByRole('button', { name: 'cached suggestion' })).toBeInTheDocument()
expect(mockFetch).not.toHaveBeenCalled()
})
it('should gracefully handle empty response structure', async () => {
// Simulate an empty response with no suggestions array
// eslint-disable-next-line ts/no-explicit-any
mockFetch.mockResolvedValue({} as any)
render(<ExamplePrompts mode="workflow" onSelect={vi.fn()} />)
expect(await screen.findByRole('button', { name: /workflowGenerator\.examples\.workflow\.summarize/i })).toBeInTheDocument()
})
it('does not double-fetch on re-renders (simulating React Strict Mode)', async () => {
mockFetch.mockResolvedValue({ suggestions: ['test double-fetch'] })
const { rerender, unmount } = render(<ExamplePrompts mode="workflow" onSelect={vi.fn()} />)
// Re-render the same component instance with a new prop to trigger the effect again
rerender(<ExamplePrompts mode="advanced-chat" onSelect={vi.fn()} />)
await screen.findByRole('button', { name: 'test double-fetch' })
expect(mockFetch).toHaveBeenCalledTimes(1) // Only fetched once because didInit.current is true
unmount()
})
})
describe('refresh', () => {
// The ↻ control pulls a fresh set — the whole point of "more ideas".
it('should refetch a fresh set when the refresh control is clicked', async () => {
mockFetch.mockResolvedValue({ suggestions: ['first set'] })
const user = userEvent.setup()
render(<ExamplePrompts mode="workflow" onSelect={vi.fn()} />)
await screen.findByRole('button', { name: 'first set' })
expect(mockFetch).toHaveBeenCalledTimes(1)
mockFetch.mockResolvedValue({ suggestions: ['second set'] })
await user.click(screen.getByTestId('workflow-gen-suggestions-refresh'))
expect(await screen.findByRole('button', { name: 'second set' })).toBeInTheDocument()
expect(mockFetch).toHaveBeenCalledTimes(2)
})
})
describe('selection', () => {
// Clicking a chip is the whole point of the component — it must hand
// the chip text back to the parent verbatim so the parent can populate
// Clicking a chip hands its text back to the parent verbatim to populate
// the instruction textarea.
it('should forward the clicked chip\'s text to onSelect', async () => {
mockFetch.mockResolvedValue({ suggestions: ['Build a triage bot'] })
const user = userEvent.setup()
const onSelect = vi.fn()
render(<ExamplePrompts mode="workflow" onSelect={onSelect} />)
const chip = screen.getByRole('button', { name: /workflowGenerator\.examples\.workflow\.summarize/i })
const chip = await screen.findByRole('button', { name: 'Build a triage bot' })
await user.click(chip)
expect(onSelect).toHaveBeenCalledTimes(1)
expect(onSelect.mock.calls[0]![0]).toMatch(/workflowGenerator\.examples\.workflow\.summarize/i)
expect(onSelect).toHaveBeenCalledWith('Build a triage bot')
})
})
})
@@ -1,92 +0,0 @@
import { act, render, screen } from '@testing-library/react'
import GenerationPhases from '../generation-phases'
describe('GenerationPhases', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
// The first frame the user sees during generation must be the "planning"
// phase — never an empty container or a different phase — so the perceived
// latency starts dropping immediately.
it('should start on the planning phase', () => {
render(<GenerationPhases startedAt={1} />)
expect(screen.getByText(/phases\.planning/i)).toBeInTheDocument()
})
// After the planner timer elapses we move to "building". The component
// doesn't reset to "planning" if the parent stays mounted — the timer
// chain only steps forward.
it('should advance to the building phase after the planning timer', () => {
render(<GenerationPhases startedAt={1} />)
act(() => {
vi.advanceTimersByTime(3500)
})
expect(screen.getByText(/phases\.building/i)).toBeInTheDocument()
expect(screen.queryByText(/phases\.planning/i)).not.toBeInTheDocument()
})
// The validating phase is the last in the schedule; once we get there we
// stay there indefinitely so a slow LLM doesn't make the indicator loop
// backwards and confuse the user.
it('should land on validating and not loop back to planning even after long delays', () => {
render(<GenerationPhases startedAt={1} />)
// Advance through phases in two steps — React schedules the next
// ``setTimeout`` only after the prior effect re-runs with the new
// ``phaseIndex``, so a single combined advance leaves us mid-phase.
act(() => {
vi.advanceTimersByTime(3500)
})
act(() => {
vi.advanceTimersByTime(12000)
})
expect(screen.getByText(/phases\.validating/i)).toBeInTheDocument()
act(() => {
vi.advanceTimersByTime(60000)
})
// Still validating — no reset, no loop.
expect(screen.getByText(/phases\.validating/i)).toBeInTheDocument()
expect(screen.queryByText(/phases\.planning/i)).not.toBeInTheDocument()
})
// Unmount cleanup matters because the modal is destroyed when the user
// closes it mid-generation; lingering timers would keep firing setState on
// an unmounted tree.
it('should not leak a timer when unmounted before the next phase fires', () => {
const { unmount } = render(<GenerationPhases startedAt={1} />)
// Sanity: pending timer should exist.
expect(vi.getTimerCount()).toBeGreaterThan(0)
unmount()
expect(vi.getTimerCount()).toBe(0)
})
// A second Generate click bumps ``startedAt``. The component must reset to
// "Planning" so the indicator doesn't appear wedged on "Validating" from
// the previous attempt. Without this the user thinks the system is stuck.
it('should reset to the planning phase when startedAt changes', () => {
const { rerender } = render(<GenerationPhases startedAt={1} />)
// Drive the first attempt all the way to validating.
act(() => {
vi.advanceTimersByTime(3500)
})
act(() => {
vi.advanceTimersByTime(12000)
})
expect(screen.getByText(/phases\.validating/i)).toBeInTheDocument()
// New attempt starts → bump startedAt. The component should snap back
// to planning rather than staying on validating.
rerender(<GenerationPhases startedAt={2} />)
expect(screen.getByText(/phases\.planning/i)).toBeInTheDocument()
expect(screen.queryByText(/phases\.validating/i)).not.toBeInTheDocument()
})
})
@@ -0,0 +1,63 @@
import type { WorkflowGenPlan } from '@/service/debug'
import { render, screen } from '@testing-library/react'
import GenerationPlan from '../generation-plan'
describe('GenerationPlan', () => {
// Before the planner returns, the right pane shows the "Planning…" phase so
// the user sees real progress rather than a bare spinner.
it('shows the planning state while the plan is null', () => {
render(<GenerationPlan plan={null} />)
expect(screen.getByText(/workflowGenerator\.phases\.planning/i)).toBeInTheDocument()
})
// Once the plan streams in, the outline (node purposes + identity) renders and
// the footer flips to "Building…" while the builder fills in the graph.
it('renders the plan outline and the building state once the plan lands', () => {
const plan: WorkflowGenPlan = {
app_name: 'URL Summarizer',
description: 'Summarize any URL',
icon: '📰',
mode: 'workflow',
nodes: [
{ label: 'Start', node_type: 'start', purpose: 'capture the URL' },
{ label: 'Summarize', node_type: 'llm', purpose: 'summarize the page' },
],
start_inputs: [{ variable: 'url', label: 'URL', type: 'text-input' }],
}
render(<GenerationPlan plan={plan} />)
expect(screen.getByText('URL Summarizer')).toBeInTheDocument()
expect(screen.getByText('capture the URL')).toBeInTheDocument()
expect(screen.getByText('summarize the page')).toBeInTheDocument()
expect(screen.getByText(/workflowGenerator\.phases\.building/i)).toBeInTheDocument()
// The planning-only state must be gone once a plan is present.
expect(screen.queryByText(/workflowGenerator\.phases\.planning/i)).not.toBeInTheDocument()
})
it('renders correctly when plan has no icon, app_name, or title', () => {
const plan: WorkflowGenPlan = {
mode: 'workflow',
nodes: [
{ label: 'Start', node_type: 'start' },
],
start_inputs: [],
} as unknown as WorkflowGenPlan
render(<GenerationPlan plan={plan} />)
expect(screen.getByText('Start')).toBeInTheDocument()
expect(screen.getByText(/workflowGenerator\.phases\.building/i)).toBeInTheDocument()
})
it('falls back to plan.title when plan.app_name is empty', () => {
const plan: WorkflowGenPlan = {
app_name: '',
title: 'Fallback Title',
mode: 'workflow',
nodes: [],
start_inputs: [],
} as unknown as WorkflowGenPlan
render(<GenerationPlan plan={plan} />)
expect(screen.getByText('Fallback Title')).toBeInTheDocument()
})
})
@@ -0,0 +1,44 @@
import type { GeneratedGraph } from '../types'
import { diffGraphs } from '../graph-diff'
type GraphNode = GeneratedGraph['nodes'][number]
// diffGraphs only reads `id` and `data`, so a minimal node shape is enough.
const node = (id: string, data: Record<string, unknown> = {}): GraphNode =>
({ id, data } as unknown as GraphNode)
const graph = (nodes: GraphNode[]): GeneratedGraph =>
({ nodes, edges: [], viewport: { x: 0, y: 0, zoom: 1 } })
describe('diffGraphs', () => {
it('reports nodes added in the new graph', () => {
const result = diffGraphs(graph([node('a')]), graph([node('a'), node('b')]))
expect(result.added).toEqual(['b'])
expect(result.removed).toEqual([])
expect(result.changed).toEqual([])
})
it('reports nodes dropped from the base graph', () => {
const result = diffGraphs(graph([node('a'), node('b')]), graph([node('a')]))
expect(result.removed).toEqual(['b'])
expect(result.added).toEqual([])
})
it('reports nodes whose data changed', () => {
const result = diffGraphs(graph([node('a', { temperature: 0.2 })]), graph([node('a', { temperature: 0.9 })]))
expect(result.changed).toEqual(['a'])
})
it('treats identical data as unchanged', () => {
const result = diffGraphs(graph([node('a', { temperature: 0.2 })]), graph([node('a', { temperature: 0.2 })]))
expect(result.changed).toEqual([])
})
it('handles a mix of added, removed and changed at once', () => {
const base = graph([node('keep', { v: 1 }), node('drop'), node('edit', { v: 1 })])
const next = graph([node('keep', { v: 1 }), node('edit', { v: 2 }), node('new')])
const result = diffGraphs(base, next)
expect(result.added).toEqual(['new'])
expect(result.removed).toEqual(['drop'])
expect(result.changed).toEqual(['edit'])
})
})
@@ -151,5 +151,36 @@ describe('useWorkflowGeneratorStore', () => {
expect(sessionStorage.getItem('workflow-gen-workflow-app-42-versions')).not.toBeNull()
})
it('should silently handle sessionStorage.removeItem throwing (e.g. privacy restrictions)', () => {
const { result } = renderHook(() => useWorkflowGeneratorStore())
const spy = vi.spyOn(sessionStorage, 'removeItem').mockImplementation(() => {
throw new Error('Access denied')
})
act(() => {
result.current.openGenerator({ mode: 'workflow' })
})
expect(spy).toHaveBeenCalled()
spy.mockRestore()
})
it('should exit early if window is undefined (e.g. SSR)', () => {
const originalWindow = globalThis.window
// @ts-expect-error - simulating SSR environment
delete globalThis.window
const spy = vi.spyOn(sessionStorage, 'removeItem')
// Call it directly without React's act to avoid React DOM crashing
useWorkflowGeneratorStore.getState().openGenerator({ mode: 'workflow' })
expect(useWorkflowGeneratorStore.getState().isOpen).toBe(true)
expect(spy).not.toHaveBeenCalled()
globalThis.window = originalWindow
spy.mockRestore()
})
})
})
@@ -14,6 +14,24 @@ const makeVersion = (marker: string): GenerateWorkflowResponse => ({
describe('useGenGraph', () => {
beforeEach(() => {
sessionStorage.clear()
vi.restoreAllMocks()
})
it('should handle undefined versions and index gracefully (e.g. during hydration or bad storage)', () => {
// If sessionStorage contains the literal string "null", ahooks returns null
sessionStorage.setItem('workflow-gen-workflow-test-versions', 'null')
sessionStorage.setItem('workflow-gen-workflow-test-version-index', 'null')
const { result } = renderHook(() => useGenGraph({ storageKey: 'workflow-test' }))
expect(result.current.currentVersionIndex).toBe(0)
expect(result.current.current).toBeUndefined()
act(() => {
result.current.addVersion(makeVersion('v1'))
})
expect(result.current.versions).toHaveLength(1)
})
describe('addVersion', () => {
@@ -1,33 +1,43 @@
'use client'
import type { WorkflowGeneratorMode } from './types'
import { memo, useMemo } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { RiRefreshLine } from '@remixicon/react'
import { useSessionStorageState } from 'ahooks'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { fetchWorkflowInstructionSuggestions } from '@/service/debug'
type Props = Readonly<{
mode: WorkflowGeneratorMode
onSelect: (prompt: string) => void
}>
/**
* "Try one of these" chips that sit below the instruction textarea.
*
* For brand-new users the blank instruction box is intimidating — they don't
* know what kinds of prompts the planner handles well. The chips give them
* a one-click way to populate a real prompt so they can see the modal end-
* to-end on their first attempt.
*
* The four prompts per mode are intentionally chosen to cover a spread of
* shapes:
* - workflow: summarization, translation, RAG, classification.
* - advanced-chat: support agent, tutor, triage.
*
* The strings live in i18n so they translate alongside the rest of the
* generator UI.
*/
const ExamplePrompts: React.FC<Props> = ({ mode, onSelect }) => {
const { t } = useTranslation('workflow')
const SUGGESTION_COUNT = 4
// Placeholder pill widths (px) while suggestions stream in — varied so the
// skeleton reads like a row of chips rather than a progress bar.
const SKELETON_WIDTHS = [88, 132, 104, 120]
const prompts = useMemo(() => {
// AbortController throws a DOMException in modern browsers and a plain Error in
// older / non-DOM environments — accept both so a user-triggered abort (modal
// close / regenerate) never surfaces as an error.
const isAbortError = (e: unknown): boolean =>
(e instanceof DOMException || e instanceof Error) && e.name === 'AbortError'
/**
* "Ideas for you" chips under the instruction textarea.
*
* Primary content is AI-generated, workspace-grounded suggestions fetched when
* the modal opens (cached per session per mode); a ↻ refresh pulls a fresh set.
* When the backend can't generate (no default model, quota, parse failure) it
* returns an empty list and we silently fall back to a curated static list, so
* the row is never empty. Create-only — the parent hides it in refine mode.
*/
const ExamplePrompts = ({ mode, onSelect }: Props) => {
const { t, i18n } = useTranslation('workflow')
// Curated fallback, shown until AI suggestions arrive and whenever generation
// is unavailable. The spread per mode covers a range of workflow shapes.
const staticPrompts = useMemo(() => {
if (mode === 'workflow') {
return [
t('workflowGenerator.examples.workflow.summarize'),
@@ -43,22 +53,96 @@ const ExamplePrompts: React.FC<Props> = ({ mode, onSelect }) => {
]
}, [mode, t])
// Session-cached AI suggestions, keyed per mode so Workflow / Chatflow don't
// clobber each other and a reopen within the same session skips the refetch.
const [cached, setCached] = useSessionStorageState<string[]>(
`workflow-gen-suggestions-${mode}`,
{ defaultValue: [] },
)
const [isLoading, setIsLoading] = useState(false)
const abortRef = useRef<AbortController | null>(null)
const didInit = useRef(false)
const fetchSuggestions = useCallback(async () => {
abortRef.current?.abort()
setIsLoading(true)
try {
const res = await fetchWorkflowInstructionSuggestions(
{ mode, language: i18n.language, count: SUGGESTION_COUNT },
{ getAbortController: (c) => { abortRef.current = c } },
)
const next = (res?.suggestions ?? []).map(s => s.trim()).filter(Boolean)
// Keep the previous set on an empty refresh so the row never flashes empty.
if (next.length)
setCached(next)
}
catch (e) {
if (isAbortError(e))
return
// Silent: the static fallback keeps the row populated.
}
finally {
setIsLoading(false)
abortRef.current = null
}
}, [mode, i18n.language, setCached])
// Auto-fetch once on open when nothing is cached for this mode yet. ``mode``
// is fixed per open (the modal remounts each time), so a mount-only effect is
// correct here.
useEffect(() => {
if (didInit.current)
return
didInit.current = true
if (!cached || cached.length === 0)
void fetchSuggestions()
return () => {
abortRef.current?.abort()
abortRef.current = null
}
// eslint-disable-next-line react/exhaustive-deps
}, [mode])
const aiPrompts = cached ?? []
const prompts = aiPrompts.length ? aiPrompts : staticPrompts
return (
<div className="mt-3">
<div className="mb-1.5 system-xs-medium-uppercase text-text-tertiary">
{t('workflowGenerator.examples.label')}
<div className="mb-1.5 flex items-center gap-1">
<span className="system-xs-medium-uppercase text-text-tertiary">
{t('workflowGenerator.examples.label')}
</span>
<button
type="button"
data-testid="workflow-gen-suggestions-refresh"
aria-label={t('workflowGenerator.examples.refresh')}
title={t('workflowGenerator.examples.refresh')}
className="flex size-4 cursor-pointer items-center justify-center rounded text-text-quaternary hover:text-text-tertiary disabled:cursor-not-allowed disabled:opacity-50"
onClick={() => { void fetchSuggestions() }}
disabled={isLoading}
>
<RiRefreshLine className={cn('size-3.5', isLoading && 'animate-spin')} />
</button>
</div>
<div className="flex flex-wrap gap-1.5">
{prompts.map(prompt => (
<button
key={prompt}
type="button"
className="cursor-pointer rounded-md border-[0.5px] border-divider-regular bg-components-button-secondary-bg px-2 py-1 system-xs-regular text-text-secondary hover:bg-components-button-secondary-bg-hover"
onClick={() => onSelect(prompt)}
>
{prompt}
</button>
))}
{isLoading
? SKELETON_WIDTHS.map((w, i) => (
<div
key={i}
className="h-[26px] animate-pulse rounded-md bg-components-button-secondary-bg"
style={{ width: w }}
/>
))
: prompts.map(prompt => (
<button
key={prompt}
type="button"
className="cursor-pointer rounded-md border-[0.5px] border-divider-regular bg-components-button-secondary-bg px-2 py-1 system-xs-regular text-text-secondary hover:bg-components-button-secondary-bg-hover"
onClick={() => onSelect(prompt)}
>
{prompt}
</button>
))}
</div>
</div>
)
@@ -1,72 +0,0 @@
'use client'
import { memo, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Loading from '@/app/components/base/loading'
/**
* Approximate stage durations (ms) for the slim planner→builder pipeline.
*
* The endpoint is single-shot — we don't get real per-phase events from the
* backend — but the user perception of "the system is doing things" is much
* better than a static spinner. The schedule below targets the typical
* 15–18 s response time. If the real response lands earlier the modal
* unmounts this component; if it lands later we hold on the last phase
* indefinitely (rather than cycling back) so the user doesn't think we
* restarted.
*/
const PLANNING_MS = 3500
const BUILDING_MS = 12000
type Props = Readonly<{
/**
* Per-attempt nonce — typically ``Date.now()`` of when Generate was
* clicked. The component resets ``phaseIndex`` whenever this changes so a
* second Generate click starts the indicator from "Planning…" instead of
* resuming wherever the previous attempt left off.
*/
startedAt: number
}>
const GenerationPhases = ({ startedAt }: Props) => {
const { t } = useTranslation('workflow')
const [phaseIndex, setPhaseIndex] = useState(0)
// Reset the indicator whenever a new attempt starts. Without this, a
// failed first attempt followed by a quick retry would resume mid-phase
// (or stuck on "Validating…") which looks like the system is wedged.
// ``set-state-in-effect`` flags this pattern, but the reset is the
// intent — driven by an external prop change, not by render-time state.
useEffect(() => {
// eslint-disable-next-line react/set-state-in-effect
setPhaseIndex(0)
}, [startedAt])
useEffect(() => {
if (phaseIndex === 0) {
const timer = setTimeout(() => setPhaseIndex(1), PLANNING_MS)
return () => clearTimeout(timer)
}
if (phaseIndex === 1) {
const timer = setTimeout(() => setPhaseIndex(2), BUILDING_MS)
return () => clearTimeout(timer)
}
// phaseIndex === 2 — terminal phase, no further timer.
}, [phaseIndex])
const label = (() => {
if (phaseIndex === 0)
return t('workflowGenerator.phases.planning')
if (phaseIndex === 1)
return t('workflowGenerator.phases.building')
return t('workflowGenerator.phases.validating')
})()
return (
<div className="flex h-full w-0 grow flex-col items-center justify-center space-y-3">
<Loading />
<div className="text-[13px] text-text-tertiary">{label}</div>
</div>
)
}
export default memo(GenerationPhases)
@@ -0,0 +1,111 @@
'use client'
import type { BlockEnum } from '@/app/components/workflow/types'
import type { WorkflowGenPlan } from '@/service/debug'
import { RiLoader4Line } from '@remixicon/react'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { SkeletonContainer, SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton'
import BlockIcon from '@/app/components/workflow/block-icon'
type Props = Readonly<{
/**
* The planner result once it has streamed in, or ``null`` while the planner
* is still running. Drives the two honest phases of the generation:
* "Planning…" (skeleton outline) → plan outline + "Building…".
*/
plan: WorkflowGenPlan | null
}>
// Stable keys for the planning skeleton's placeholder rows — avoids array-index
// keys while still rendering a fixed-length outline.
const SKELETON_ROWS = ['s1', 's2', 's3', 's4'] as const
// While the planner runs we render a skeleton shaped like the node list that's
// about to arrive, using the shared Skeleton primitives. The pane fills in
// place instead of jerking from a centred spinner to a left-aligned list.
const PlanningSkeleton = memo(() => {
const { t } = useTranslation('workflow')
return (
<div className="flex h-full w-0 grow flex-col bg-background-default-subtle p-6">
<SkeletonRow className="mb-3">
<SkeletonRectangle className="size-5 rounded-md" />
<SkeletonRectangle className="h-3 w-40" />
</SkeletonRow>
<div className="grow overflow-hidden rounded-2xl border border-divider-subtle bg-background-default p-4">
<SkeletonContainer className="gap-3">
{SKELETON_ROWS.map(key => (
<SkeletonRow key={key} className="items-start">
<SkeletonRectangle className="size-6 shrink-0 rounded-lg" />
<div className="flex grow flex-col gap-1.5">
<SkeletonRectangle className="h-3 w-1/3" />
<SkeletonRectangle className="h-2 w-2/3" />
</div>
</SkeletonRow>
))}
</SkeletonContainer>
</div>
<div className="mt-3 flex items-center gap-1.5 text-[13px] text-text-tertiary">
<RiLoader4Line className="size-4 animate-spin" />
<span>{t('workflowGenerator.phases.planning')}</span>
</div>
</div>
)
})
PlanningSkeleton.displayName = 'PlanningSkeleton'
/**
* Plan-first loading view for the generator's right pane.
*
* Replaces the old guessed phase timer (``generation-phases``): the backend now
* streams the real planner result, so we show a skeleton outline until it
* arrives, then the actual node outline — rendered with the shared workflow
* ``BlockIcon`` so each step shows the same icon the user will see on the
* canvas — while the builder fills in the graph.
*/
const GenerationPlan = ({ plan }: Props) => {
const { t } = useTranslation('workflow')
if (!plan)
return <PlanningSkeleton />
return (
<div className="flex h-full w-0 grow flex-col bg-background-default-subtle p-6">
{(plan.icon || plan.app_name || plan.title) && (
<div className="mb-3 flex items-center gap-2">
{plan.icon && <span className="text-xl leading-none">{plan.icon}</span>}
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-text-primary">{plan.app_name || plan.title}</div>
{plan.description && <div className="truncate system-xs-regular text-text-tertiary">{plan.description}</div>}
</div>
</div>
)}
<div className="grow overflow-y-auto rounded-2xl border border-divider-subtle bg-background-default p-4">
<ol className="space-y-2.5">
{plan.nodes.map((node, index) => (
<li key={`${node.label}-${index}`} className="flex items-start gap-2.5">
<BlockIcon type={node.node_type as BlockEnum} size="md" className="mt-px shrink-0" />
<div className="min-w-0">
<div className="system-sm-medium text-text-secondary">
{node.label}
<span className="ml-1 system-xs-regular text-text-quaternary">
·
{node.node_type}
</span>
</div>
{node.purpose && <div className="system-xs-regular text-text-tertiary">{node.purpose}</div>}
</div>
</li>
))}
</ol>
</div>
<div className="mt-3 flex items-center gap-1.5 text-[13px] text-text-tertiary">
<RiLoader4Line className="size-4 animate-spin" />
<span>{t('workflowGenerator.phases.building')}</span>
</div>
</div>
)
}
export default memo(GenerationPlan)
@@ -0,0 +1,39 @@
import type { GeneratedGraph } from './types'
export type GraphDiff = {
/** Node ids present in the new graph but not the base. */
added: string[]
/** Node ids present in the base graph but dropped from the new one. */
removed: string[]
/** Node ids present in both whose ``data`` changed. */
changed: string[]
}
/**
* Shallow node-level diff between a refine base graph and the generated result.
*
* Used by the `/refine` flow to tell the user what an "apply" would actually
* change before they overwrite their draft — far less scary than a bare "this
* cannot be undone". Comparison is by node ``id`` with a JSON equality check on
* ``data``; edges and layout (which the generator always rewrites) are ignored
* so cosmetic re-layouts don't read as changes.
*/
export const diffGraphs = (base: GeneratedGraph, next: GeneratedGraph): GraphDiff => {
const baseById = new Map(base.nodes.map(node => [node.id, node]))
const nextById = new Map(next.nodes.map(node => [node.id, node]))
const added: string[] = []
const changed: string[] = []
for (const [id, node] of nextById) {
const prev = baseById.get(id)
if (!prev) {
added.push(id)
continue
}
if (JSON.stringify(prev.data) !== JSON.stringify(node.data))
changed.push(id)
}
const removed = [...baseById.keys()].filter(id => !nextById.has(id))
return { added, removed, changed }
}
@@ -1,6 +1,7 @@
'use client'
import type { GeneratedGraph } from './types'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { GenerateWorkflowBody, GenerateWorkflowResponse as StreamResult, WorkflowGenPlan } from '@/service/debug'
import type { CompletionParams, ModelModeType } from '@/types/app'
import {
AlertDialog,
@@ -15,12 +16,12 @@ import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { toast } from '@langgenius/dify-ui/toast'
import { RiErrorWarningLine } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import IdeaOutput from '@/app/components/app/configuration/config/automatic/idea-output'
import VersionSelector from '@/app/components/app/configuration/config/automatic/version-selector'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
@@ -28,13 +29,14 @@ import ModelParameterModal from '@/app/components/header/account-setting/model-p
import WorkflowPreview from '@/app/components/workflow/workflow-preview'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useRouter } from '@/next/navigation'
import { generateWorkflow } from '@/service/debug'
import { generateWorkflow, generateWorkflowStream } from '@/service/debug'
import { fetchWorkflowDraft } from '@/service/workflow'
import { getRedirectionPath } from '@/utils/app-redirection'
import { applyToCurrentApp, applyToNewApp, WorkflowApplyHashCollisionError, WorkflowApplyOrphanError } from './apply'
import ExamplePrompts from './example-prompts'
import GenerationPhases from './generation-phases'
import { EMPTY_WORKFLOW_GENERATOR_MODEL, useWorkflowGeneratorModel } from './storage'
import GenerationPlan from './generation-plan'
import { diffGraphs } from './graph-diff'
import { EMPTY_WORKFLOW_GENERATOR_MODEL, useWorkflowGeneratorLastInstruction, useWorkflowGeneratorModel } from './storage'
import { useWorkflowGeneratorStore } from './store'
import useGenGraph from './use-gen-graph'
@@ -48,6 +50,11 @@ const FE_TIMEOUT_MS = 90_000
// keeping the limit client-side turns an opaque 400 into a visible input stop.
const MAX_INSTRUCTION_LENGTH = 10_000
// A single structured generation error. Mirrors the backend ``errors[]`` entry
// (stable ``code`` + human ``detail`` + optional ``node_id``) so the error panel
// can localise the message and point at the offending node.
type GenError = { code: string, detail: string, node_id?: string }
const renderPlaceholder = (label: string) => (
<div className="flex h-full w-0 grow flex-col items-center justify-center space-y-3 px-8">
<span className="i-custom-vender-other-generator size-8 text-text-quaternary" />
@@ -107,6 +114,8 @@ const WorkflowGeneratorModal: React.FC = () => {
const intent = useWorkflowGeneratorStore(s => s.intent)
const currentAppId = useWorkflowGeneratorStore(s => s.currentAppId)
const currentAppMode = useWorkflowGeneratorStore(s => s.currentAppMode)
const initialInstruction = useWorkflowGeneratorStore(s => s.initialInstruction)
const autoMode = useWorkflowGeneratorStore(s => s.autoMode)
const closeGenerator = useWorkflowGeneratorStore(s => s.closeGenerator)
const isRefine = intent === 'refine' && !!currentAppId
@@ -144,8 +153,19 @@ const WorkflowGeneratorModal: React.FC = () => {
}))
}, [setModel])
const [instruction, setInstruction] = useState('')
const [ideaOutput, setIdeaOutput] = useState('')
const [lastInstruction, setLastInstruction] = useWorkflowGeneratorLastInstruction()
// Seed from the palette's inline-captured instruction, else the last instruction
// generated from (persisted across opens). Captured at mount only — the modal
// remounts on each open, so this is just the initial value.
const [instruction, setInstruction] = useState(initialInstruction || lastInstruction || '')
// Planner result, streamed ahead of the graph (null until it lands).
const [plan, setPlan] = useState<WorkflowGenPlan | null>(null)
// Structured generation errors (validation / model). Drives the actionable
// error panel; null when the last attempt succeeded or hasn't run.
const [genError, setGenError] = useState<GenError[] | null>(null)
// Refine base graph captured at Generate time, diffed against the result so
// the user can see what "apply" changes before overwriting their draft.
const [refineBaseGraph, setRefineBaseGraph] = useState<GeneratedGraph | null>(null)
const storageKey = `${mode}-${currentAppId ?? 'new'}`
const { addVersion, current, currentVersionIndex, setCurrentVersionIndex, versions } = useGenGraph({
@@ -155,11 +175,6 @@ const WorkflowGeneratorModal: React.FC = () => {
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false)
const [isApplying, { setTrue: setApplyingTrue, setFalse: setApplyingFalse }] = useBoolean(false)
// Per-attempt nonce — bumped on each Generate click so ``GenerationPhases``
// can reset its internal phase timer instead of resuming wherever the
// previous attempt left off (which makes the UI look wedged).
const [startedAt, setStartedAt] = useState(0)
// Confirmation dialog for "Apply to current draft"
const [isShowConfirmOverwrite, { setTrue: showConfirmOverwrite, setFalse: hideConfirmOverwrite }] = useBoolean(false)
@@ -213,7 +228,7 @@ const WorkflowGeneratorModal: React.FC = () => {
}, [])
// Note: the modal is mounted lazily by ``mount.tsx`` which unmounts it when
// ``isOpen`` flips to false, so transient state (instruction / ideaOutput)
// ``isOpen`` flips to false, so transient state (instruction / plan / errors)
// resets implicitly on the next open. No reset effect needed.
const isValid = () => {
@@ -233,93 +248,118 @@ const WorkflowGeneratorModal: React.FC = () => {
return true
}
// Apply a finished generation result (from the stream's ``result`` event or
// the non-streaming fallback). Structured errors go to the actionable error
// panel rather than a transient toast; a version is added only for a real graph.
const handleResult = useCallback((res: StreamResult) => {
if (res.errors?.length) {
setGenError(res.errors as GenError[])
return
}
if (!res.graph?.nodes?.length) {
setGenError([{ code: 'EMPTY', detail: res.error || t('workflowGenerator.generateFailed') }])
return
}
setGenError(null)
addVersion(res)
}, [addVersion, t])
const onGenerate = async () => {
if (!isValid())
return
// Cancel any previous in-flight request (double-click guard). The
// previous promise will reject with AbortError which our catch swallows.
// Cancel any previous in-flight request (double-click guard).
abortInFlight()
setStartedAt(Date.now())
generatedModeRef.current = mode
setLastInstruction(instruction)
setGenError(null)
setPlan(null)
setLoadingTrue()
// Hard frontend timeout — aborts the request and surfaces a localised
// toast so the user sees something actionable instead of a perpetual
// spinner if the backend hangs.
// Hard frontend timeout — aborts the request and surfaces a localised toast
// instead of a perpetual spinner if the backend hangs.
timeoutRef.current = setTimeout(() => {
abortRef.current?.abort()
abortRef.current = null
toast.error(t('workflowGenerator.errors.timeout'))
setLoadingFalse()
}, FE_TIMEOUT_MS)
try {
// Refine mode: pull the current draft graph so the backend amends it
// instead of starting from scratch. The modal mounts outside the Studio's
// ReactFlow provider, so we read the persisted draft rather than the live
// canvas. A fetch failure (no draft saved yet) degrades gracefully to a
// from-scratch generation — better than blocking the user entirely — but
// the user asked to REFINE, so tell them their draft isn't being used
// instead of silently generating something unrelated.
let currentGraph: Awaited<ReturnType<typeof fetchWorkflowDraft>>['graph'] | undefined
if (isRefine && currentAppId) {
try {
const draft = await fetchWorkflowDraft(`apps/${currentAppId}/workflows/draft`)
if (draft?.graph?.nodes?.length)
currentGraph = draft.graph
}
catch {
currentGraph = undefined
}
if (!currentGraph)
toast.warning(t('workflowGenerator.refineDraftUnavailable'))
// Refine mode: pull the current draft so the backend amends it instead of
// starting from scratch. The modal mounts outside the Studio's ReactFlow
// provider, so we read the persisted draft rather than the live canvas. A
// fetch failure (no draft yet) degrades to from-scratch generation, but we
// warn since the user explicitly asked to refine.
let currentGraph: GeneratedGraph | undefined
if (isRefine && currentAppId) {
try {
const draft = await fetchWorkflowDraft(`apps/${currentAppId}/workflows/draft`)
if (draft?.graph?.nodes?.length)
currentGraph = draft.graph as GeneratedGraph
}
catch {
currentGraph = undefined
}
if (!currentGraph)
toast.warning(t('workflowGenerator.refineDraftUnavailable'))
}
setRefineBaseGraph(currentGraph ?? null)
const res = await generateWorkflow({
mode,
instruction,
ideal_output: ideaOutput,
model_config: model,
...(currentGraph ? { current_graph: currentGraph } : {}),
}, {
getAbortController: (c) => { abortRef.current = c },
})
const first = res.errors?.[0]
if (first) {
// Prefer the localised copy for the structured code; fall back to
// the backend's human-readable ``detail`` for codes we don't have
// a translation for yet.
const i18nKey = `workflowGenerator.errors.${first.code}`
const localised = t(i18nKey, { defaultValue: '' })
toast.error(localised || first.detail || res.error || t('workflowGenerator.generateFailed'))
return
}
if (res.error) {
toast.error(res.error)
return
}
if (!res.graph?.nodes?.length) {
// Defensive: a success envelope with an empty graph should never
// leave the backend, but if it does, an empty "version" would just
// pollute the selector with a blank preview.
toast.error(t('workflowGenerator.generateFailed'))
return
}
addVersion(res)
const body: GenerateWorkflowBody = {
// Auto-mode sends 'auto' so the planner picks Workflow vs Chatflow; the
// resolved concrete mode comes back on the result and drives apply.
mode: autoMode ? 'auto' : mode,
instruction,
model_config: model,
...(currentGraph ? { current_graph: currentGraph } : {}),
}
catch (e: unknown) {
// Aborts are intentional (modal close, second click, timeout) — never
// toast for them. The timeout path already showed its own toast.
if (isAbortError(e))
return
const message = e instanceof Error ? e.message : ''
toast.error(message || t('workflowGenerator.generateFailed'))
}
finally {
const finish = () => {
setLoadingFalse()
clearTimers()
abortRef.current = null
}
// Plan-first streaming: surface the planner outline the moment it lands, then
// the graph. ``settled`` tracks whether the stream produced anything, so a
// stream that dies before any event (endpoint missing, proxy buffering) can
// fall back to the single-shot endpoint instead of failing the user.
let settled = false
generateWorkflowStream(body, {
getAbortController: (c) => { abortRef.current = c },
onPlan: (p) => {
settled = true
setPlan(p)
},
onResult: (res) => {
settled = true
handleResult(res)
finish()
},
onError: (msg) => {
if (!settled) {
generateWorkflow(body, {
getAbortController: (c) => { abortRef.current = c },
})
.then(res => handleResult(res))
.catch((e: unknown) => {
if (isAbortError(e))
return
const message = e instanceof Error ? e.message : ''
toast.error(message || t('workflowGenerator.generateFailed'))
})
.finally(finish)
return
}
if (msg)
toast.error(msg)
finish()
},
onCompleted: () => {
if (settled)
finish()
},
})
}
const onCancelGeneration = useCallback(() => {
@@ -343,13 +383,17 @@ const WorkflowGeneratorModal: React.FC = () => {
setApplyingTrue()
try {
const { appId, appMode, permissionKeys } = await applyToNewApp({
mode,
// Resolved mode — when the request used auto-mode this is the concrete
// type the planner picked, so the new app is created as the right kind.
mode: current.mode ?? mode,
graph: current.graph as GeneratedGraph,
instruction,
appName: current.app_name,
icon: current.icon,
})
toast.success(t('workflowGenerator.applied'))
// Nudge the freshly-created Studio toward iterating with cmd+k /refine
// instead of regenerating from scratch for a small tweak.
toast.success(t('workflowGenerator.appliedRefineHint'))
closeGenerator()
router.push(getRedirectionPath({ id: appId, mode: appMode, permission_keys: permissionKeys }, { isRbacEnabled }))
}
@@ -405,6 +449,21 @@ const WorkflowGeneratorModal: React.FC = () => {
const modeLabel = mode === 'workflow' ? t('workflowGenerator.modes.workflow') : t('workflowGenerator.modes.chatflow')
// Refine diff — what an "apply" would change vs. the draft we started from.
const refineDiff = useMemo(() => {
if (!isRefine || !refineBaseGraph || !current?.graph?.nodes?.length)
return null
return diffGraphs(refineBaseGraph, current.graph as GeneratedGraph)
}, [isRefine, refineBaseGraph, current])
const hasRefineChanges = !!refineDiff && (refineDiff.added.length > 0 || refineDiff.removed.length > 0 || refineDiff.changed.length > 0)
// Derived view of the last structured error for the actionable error panel.
const firstGenError = genError?.[0]
const genErrorMessage = firstGenError
? (t(`workflowGenerator.errors.${firstGenError.code}`, { defaultValue: '' }) || firstGenError.detail || t('workflowGenerator.generateFailed'))
: ''
const genErrorHasUnknownTool = !!genError?.some(e => e.code === 'UNKNOWN_TOOL')
return (
<Dialog
open={isOpen}
@@ -451,12 +510,25 @@ const WorkflowGeneratorModal: React.FC = () => {
{t('workflowGenerator.instruction')}
</div>
<Textarea
// Autofocus is appropriate here: the modal's sole purpose is to
// capture an instruction, so focusing it on open aids the flow.
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
className="h-[160px]"
placeholder={isRefine
? t('workflowGenerator.refineInstructionPlaceholder')
: t('workflowGenerator.instructionPlaceholder')}
value={instruction}
onValueChange={setInstruction}
onKeyDown={(e) => {
// ⌘/Ctrl+Enter generates — the journey starts keyboard-first in
// the palette, so let it finish without reaching for the mouse.
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
e.preventDefault()
if (!isLoading)
onGenerate()
}
}}
maxLength={MAX_INSTRUCTION_LENGTH}
/>
@@ -465,11 +537,6 @@ const WorkflowGeneratorModal: React.FC = () => {
them in refine mode. */}
{!isRefine && <ExamplePrompts mode={mode} onSelect={setInstruction} />}
<IdeaOutput
value={ideaOutput}
onChange={setIdeaOutput}
/>
<div className="mt-7 flex justify-end space-x-2">
<Button onClick={closeGenerator}>
{t('workflowGenerator.dismiss')}
@@ -503,65 +570,116 @@ const WorkflowGeneratorModal: React.FC = () => {
</div>
</div>
{/* Right pane: preview + version selector + apply */}
{(!isLoading && current?.graph?.nodes?.length)
{/* Right pane: planning → graph result / actionable error / empty placeholder */}
{isLoading
? (
<div className="flex h-full w-0 grow flex-col bg-background-default-subtle p-6">
<div className="mb-3 flex items-center justify-between">
<VersionSelector
versionLen={versions?.length || 0}
value={currentVersionIndex || 0}
onChange={setCurrentVersionIndex}
/>
<div className="flex items-center space-x-2">
{canApplyToCurrent
? (
// Studio button entry — overwrite the current draft
// is the only meaningful Apply action, so collapse
// the two buttons into one primary "Apply".
<Button
size="small"
variant="primary"
onClick={showConfirmOverwrite}
disabled={isApplying}
>
{t('workflowGenerator.studioApply')}
</Button>
)
: (
// cmd+k /create entry — no current-app context, so
// the only path is "Create new app".
<Button
size="small"
variant="primary"
onClick={handleApplyToNew}
disabled={isApplying}
>
{t('workflowGenerator.applyToNew')}
</Button>
)}
</div>
</div>
<div className="relative w-full grow overflow-hidden rounded-2xl border border-divider-subtle bg-background-default">
<WorkflowPreview
nodes={current.graph.nodes}
edges={current.graph.edges}
viewport={current.graph.viewport}
miniMapToRight
/>
</div>
{current.message && (
<div className="mt-2 system-xs-regular text-text-tertiary">
{current.message}
</div>
)}
</div>
<GenerationPlan plan={plan} />
)
: null}
{isLoading && <GenerationPhases startedAt={startedAt} />}
{!isLoading && !current?.graph?.nodes?.length && renderPlaceholder(t('workflowGenerator.placeholder'))}
: genError?.length
? (
<div className="flex h-full w-0 grow flex-col items-center justify-center gap-4 px-8">
<RiErrorWarningLine className="size-8 text-text-quaternary" />
<div className="text-center">
<div className="system-md-medium text-text-secondary">{genErrorMessage}</div>
{firstGenError?.node_id && (
<div className="mt-1 system-xs-regular text-text-tertiary">
{t('workflowGenerator.errors.atNode', { node: firstGenError.node_id })}
</div>
)}
</div>
<div className="flex items-center gap-2">
<Button size="small" variant="primary" onClick={onGenerate} disabled={!model.name}>
{t('workflowGenerator.regenerate')}
</Button>
{genErrorHasUnknownTool && (
<Button
size="small"
variant="secondary"
onClick={() => {
closeGenerator()
router.push('/tools')
}}
>
{t('workflowGenerator.errors.installTools')}
</Button>
)}
</div>
</div>
)
: current?.graph?.nodes?.length
? (
<div className="flex h-full w-0 grow flex-col bg-background-default-subtle p-6">
{/* Planner-picked identity — surfaces the app_name + icon the
UI used to discard so the user sees what they'll create. */}
{(current.icon || current.app_name) && (
<div className="mb-2 flex items-center gap-2">
{current.icon && <span className="text-lg leading-none">{current.icon}</span>}
{current.app_name && (
<span className="truncate text-sm font-semibold text-text-primary">{current.app_name}</span>
)}
</div>
)}
<div className="mb-3 flex items-center justify-between">
<VersionSelector
versionLen={versions?.length || 0}
value={currentVersionIndex || 0}
onChange={setCurrentVersionIndex}
/>
<div className="flex items-center space-x-2">
{canApplyToCurrent
? (
// Studio button entry — overwrite the current draft
// is the only meaningful Apply action, so collapse
// the two buttons into one primary "Apply".
<Button
size="small"
variant="primary"
onClick={showConfirmOverwrite}
disabled={isApplying}
>
{t('workflowGenerator.studioApply')}
</Button>
)
: (
// cmd+k /create entry — no current-app context, so
// the only path is "Create new app".
<Button
size="small"
variant="primary"
onClick={handleApplyToNew}
disabled={isApplying}
>
{t('workflowGenerator.applyToNew')}
</Button>
)}
</div>
</div>
<div className="relative w-full grow overflow-hidden rounded-2xl border border-divider-subtle bg-background-default">
<WorkflowPreview
nodes={current.graph.nodes}
edges={current.graph.edges}
viewport={current.graph.viewport}
miniMapToRight
/>
</div>
{/* Refine diff — what an apply changes vs. the draft we started from. */}
{hasRefineChanges && refineDiff && (
<div className="mt-2 system-xs-regular text-text-tertiary">
{t('workflowGenerator.diff.summary', {
added: refineDiff.added.length,
removed: refineDiff.removed.length,
changed: refineDiff.changed.length,
})}
</div>
)}
{current.message && (
<div className="mt-2 system-xs-regular text-text-tertiary">
{current.message}
</div>
)}
</div>
)
: renderPlaceholder(t('workflowGenerator.placeholder'))}
</div>
<RecoveryDialog
@@ -15,6 +15,16 @@ const [
_useSetWorkflowGeneratorModel,
] = createLocalStorageState<Model>('workflow-gen-model', EMPTY_WORKFLOW_GENERATOR_MODEL)
// Last instruction the user generated from, persisted across opens so reopening
// the generator resumes where they left off instead of a blank box (the palette's
// inline-captured instruction still takes precedence over this).
const [
useWorkflowGeneratorLastInstruction,
_useWorkflowGeneratorLastInstructionValue,
_useSetWorkflowGeneratorLastInstruction,
] = createLocalStorageState<string>('workflow-gen-last-instruction', '')
export {
useWorkflowGeneratorLastInstruction,
useWorkflowGeneratorModel,
}
@@ -9,11 +9,17 @@ type WorkflowGeneratorStore = {
intent: WorkflowGeneratorIntent
currentAppId: string | null
currentAppMode: WorkflowGeneratorMode | null
/** Pre-filled instruction from the palette's inline capture (`/create workflow <text>`). */
initialInstruction: string
/** When true the request uses `mode: 'auto'` so the planner picks Workflow vs Chatflow. */
autoMode: boolean
openGenerator: (params: {
mode: WorkflowGeneratorMode
intent?: WorkflowGeneratorIntent
currentAppId?: string | null
currentAppMode?: WorkflowGeneratorMode | null
initialInstruction?: string
autoMode?: boolean
}) => void
closeGenerator: () => void
}
@@ -49,10 +55,12 @@ export const useWorkflowGeneratorStore = create<WorkflowGeneratorStore>(set => (
intent: 'create',
currentAppId: null,
currentAppMode: null,
openGenerator: ({ mode, intent = 'create', currentAppId = null, currentAppMode = null }) => {
initialInstruction: '',
autoMode: false,
openGenerator: ({ mode, intent = 'create', currentAppId = null, currentAppMode = null, initialInstruction = '', autoMode = false }) => {
if (!currentAppId)
resetNewAppHistory(mode)
set({ isOpen: true, mode, intent, currentAppId, currentAppMode })
set({ isOpen: true, mode, intent, currentAppId, currentAppMode, initialInstruction, autoMode })
},
closeGenerator: () => set({ isOpen: false }),
}))
@@ -26,5 +26,11 @@ export type GenerateWorkflowResponse = {
app_name?: string
/** Planner-picked emoji icon for the new App. Empty triggers a 🤖 fallback. */
icon?: string
/**
* Resolved app mode for this generation. Echoes the requested mode, except
* when the request used `mode: 'auto'` — then it's the concrete mode the
* planner picked, used to decide which app type "Create new app" builds.
*/
mode?: WorkflowGeneratorMode
error?: string
}
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "لا يوجد تطبيق بعد",
"gotoAnything.actions.accountDesc": "الانتقال إلى صفحة الحساب",
"gotoAnything.actions.communityDesc": "فتح مجتمع Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "قم بإنشاء سير عمل أو سير دردشة تم إنشاؤه بواسطة الذكاء الاصطناعي",
"gotoAnything.actions.createChatflow": "تدفق الدردشة",
"gotoAnything.actions.createChatflowDesc": "أنشئ تطبيق تدفق الدردشة (الدردشة المتقدمة) من الوصف",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "بعد استعادة الإصدار، سيتم استبدال المسودة الحالية.",
"versionHistory.title": "الإصدارات",
"workflowGenerator.applied": "مطبق",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "فشل في تطبيق سير العمل",
"workflowGenerator.applyToCurrent": "تنطبق على المسودة الحالية",
"workflowGenerator.applyToNew": "إنشاء تطبيق جديد",
"workflowGenerator.cancel": "إلغاء",
"workflowGenerator.description": "قم بوصف ما تريد أن يفعله سير العمل. اختر نموذجًا، واكتب تعليمات، وقم بمعاينة الرسم البياني الذي تم إنشاؤه قبل تطبيقه على الاستوديو.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "استبعاد",
"workflowGenerator.errors.DANGLING_EDGE": "يحتوي سير العمل الذي تم إنشاؤه على حافة تشير إلى عقدة غير موجودة. حاول مرة أخرى أو قم بتحسين تعليماتك.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "يحتوي سير العمل المُنشأ على معرّفات عقد مكررة. حاول إعادة الإنشاء.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "يشير سير العمل إلى أداة غير مثبتة لمساحة العمل هذه. قم بتثبيته من صفحة الأدوات أو قم بتحسين التعليمات الخاصة بك.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "تشير العقدة الموجودة في سير العمل الذي تم إنشاؤه إلى متغير لم يتم الإعلان عنه في المنبع. حاول التجديد.",
"workflowGenerator.errors.apply_failed_orphan": "لم نتمكن من الانتهاء من إنشاء التطبيق. ربما تكون هناك مسودة فارغة في قائمة التطبيقات الخاصة بك - يرجى إزالتها يدويًا.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "تم تحرير مسودة سير العمل في علامة تبويب أخرى. أعد التحميل لالتقاط تلك التغييرات، ثم حاول التقديم مرة أخرى.",
"workflowGenerator.errors.hash_collision_title": "تم تحرير مساحة العمل في مكان آخر",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "استغرق الجيل وقتا طويلا. قد يكون النموذج بطيئًا أو غير متاح - حاول مرة أخرى.",
"workflowGenerator.examples.chatflow.support": "روبوت دعم العملاء مدعوم بقاعدة معرفية",
"workflowGenerator.examples.chatflow.triage": "فرز الأسئلة الواردة وتوجيهها إلى موجه متخصص",
"workflowGenerator.examples.chatflow.tutor": "مدرس متعدد اللغات يشرح خطوة بخطوة",
"workflowGenerator.examples.label": "جرب واحدة من هذه",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "جلب مشكلات GitHub وتصنيفها",
"workflowGenerator.examples.workflow.rag": "استعلام قاعدة المعرفة، ثم قم بتنسيق الإجابة كـ Markdown",
"workflowGenerator.examples.workflow.summarize": "تلخيص عنوان URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "تعذّر تحميل المسودة الحالية — سيتم الإنشاء من الصفر بدلاً من ذلك.",
"workflowGenerator.refineInstructionPlaceholder": "وصف التغيير - على سبيل المثال أضف خطوة ترجمة، وقم بالتبديل إلى أداة، وأضف معالجة الأخطاء.",
"workflowGenerator.refineTitle": "تنقيح {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "إعادة تحميل",
"workflowGenerator.studioApply": "تطبيق",
"workflowGenerator.studioButton": "إنشاء",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Noch keine App",
"gotoAnything.actions.accountDesc": "Gehe zur Kontoseite",
"gotoAnything.actions.communityDesc": "Offene Discord-Community",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Erstellen Sie einen KI-generierten Workflow oder Chatflow",
"gotoAnything.actions.createChatflow": "Chatfluss",
"gotoAnything.actions.createChatflowDesc": "Generieren Sie eine Chatflow-App (erweiterter Chat) aus einer Beschreibung",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Nach der Wiederherstellung der Version wird der aktuelle Entwurf überschrieben.",
"versionHistory.title": "Versionen",
"workflowGenerator.applied": "Angewendet",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Der Workflow konnte nicht angewendet werden",
"workflowGenerator.applyToCurrent": "Auf aktuellen Entwurf anwenden",
"workflowGenerator.applyToNew": "Neue App erstellen",
"workflowGenerator.cancel": "Abbrechen",
"workflowGenerator.description": "Beschreiben Sie, was der Workflow bewirken soll. Wählen Sie ein Modell aus, schreiben Sie eine Anweisung und zeigen Sie eine Vorschau des generierten Diagramms an, bevor Sie es auf Studio anwenden.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Entlassen",
"workflowGenerator.errors.DANGLING_EDGE": "Der generierte Workflow weist eine Kante auf, die auf einen Knoten zeigt, der nicht vorhanden ist. Versuchen Sie es noch einmal oder verfeinern Sie Ihre Anleitung.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Der generierte Workflow enthält doppelte Knoten-IDs. Versuchen Sie, ihn neu zu generieren.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "Der Workflow verweist auf ein Tool, das für diesen Arbeitsbereich nicht installiert ist. Installieren Sie es von der Seite „Tools“ oder verfeinern Sie Ihre Anweisungen.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Ein Knoten im generierten Workflow verweist auf eine Variable, die nicht im Upstream deklariert ist. Versuchen Sie, sich zu regenerieren.",
"workflowGenerator.errors.apply_failed_orphan": "Wir konnten die Erstellung der App nicht abschließen. Möglicherweise befindet sich in Ihrer Apps-Liste noch ein leerer Entwurf. Bitte entfernen Sie ihn manuell.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Der Workflow-Entwurf wurde in einem anderen Tab bearbeitet. Laden Sie die Datei neu, um die Änderungen zu übernehmen, und versuchen Sie dann erneut, sie anzuwenden.",
"workflowGenerator.errors.hash_collision_title": "Der Arbeitsbereich wurde an anderer Stelle bearbeitet",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Die Generierung dauerte zu lange. Das Modell ist möglicherweise langsam oder nicht verfügbar. Versuchen Sie es erneut.",
"workflowGenerator.examples.chatflow.support": "Kundensupport-Bot mit Unterstützung durch eine Wissensdatenbank",
"workflowGenerator.examples.chatflow.triage": "Sortieren Sie eingehende Fragen und leiten Sie sie an einen Spezialisten weiter",
"workflowGenerator.examples.chatflow.tutor": "Mehrsprachiger Tutor, der Schritt für Schritt erklärt",
"workflowGenerator.examples.label": "Probieren Sie eines davon aus",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "GitHub-Probleme abrufen und klassifizieren",
"workflowGenerator.examples.workflow.rag": "Fragen Sie die Wissensdatenbank ab und formatieren Sie die Antwort dann als Markdown",
"workflowGenerator.examples.workflow.summarize": "Fassen Sie eine URL zusammen",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Der aktuelle Entwurf konnte nicht geladen werden — es wird stattdessen von Grund auf neu generiert.",
"workflowGenerator.refineInstructionPlaceholder": "Beschreiben Sie die Änderung – z.B. Fügen Sie einen Übersetzungsschritt hinzu, wechseln Sie zu einem Tool und fügen Sie eine Fehlerbehandlung hinzu.",
"workflowGenerator.refineTitle": "Verfeinern {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Neu laden",
"workflowGenerator.studioApply": "Bewerben",
"workflowGenerator.studioButton": "Generieren",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Build your first App",
"gotoAnything.actions.accountDesc": "Navigate to account page",
"gotoAnything.actions.communityDesc": "Open Discord community",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Create an AI-generated workflow or chatflow",
"gotoAnything.actions.createChatflow": "Chatflow",
"gotoAnything.actions.createChatflowDesc": "Generate a chatflow (advanced chat) app from a description",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "After version restoration, the current draft will be overwritten.",
"versionHistory.title": "Versions",
"workflowGenerator.applied": "Applied",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Failed to apply workflow",
"workflowGenerator.applyToCurrent": "Apply to current draft",
"workflowGenerator.applyToNew": "Create new app",
"workflowGenerator.cancel": "Cancel",
"workflowGenerator.description": "Describe what you want the workflow to do. Pick a model, write an instruction, and preview the generated graph before applying it to Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Dismiss",
"workflowGenerator.errors.DANGLING_EDGE": "The generated workflow has an edge pointing at a node that doesn't exist. Try again or refine your instruction.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "The generated workflow contains duplicate node IDs. Try regenerating.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "The workflow references a tool that isn't installed for this workspace. Install it from the Tools page or refine your instruction.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "A node in the generated workflow references a variable that isn't declared upstream. Try regenerating.",
"workflowGenerator.errors.apply_failed_orphan": "We couldn't finish creating the app. An empty draft may have been left in your apps list — please remove it manually.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "The workflow draft was edited in another tab. Reload to pick up those changes, then try applying again.",
"workflowGenerator.errors.hash_collision_title": "Workspace was edited elsewhere",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Generation took too long. The model might be slow or unavailable — try again.",
"workflowGenerator.examples.chatflow.support": "Customer-support bot backed by a knowledge base",
"workflowGenerator.examples.chatflow.triage": "Triage incoming questions and route to a specialist prompt",
"workflowGenerator.examples.chatflow.tutor": "Multi-language tutor that explains step by step",
"workflowGenerator.examples.label": "Try one of these",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Fetch GitHub issues and classify them",
"workflowGenerator.examples.workflow.rag": "Knowledge-base query, then format the answer as Markdown",
"workflowGenerator.examples.workflow.summarize": "Summarize a URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Couldn't load the current draft — generating from scratch instead.",
"workflowGenerator.refineInstructionPlaceholder": "Describe the change — e.g. add a translation step, switch to a tool, add error handling.",
"workflowGenerator.refineTitle": "Refine {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Reload",
"workflowGenerator.studioApply": "Apply",
"workflowGenerator.studioButton": "Generate",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Aún no hay aplicaciones",
"gotoAnything.actions.accountDesc": "Navegar a la página de cuenta",
"gotoAnything.actions.communityDesc": "Abrir comunidad de Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Cree un flujo de trabajo o flujo de chat generado por IA",
"gotoAnything.actions.createChatflow": "Flujo de chat",
"gotoAnything.actions.createChatflowDesc": "Generar una aplicación de chatflow (chat avanzado) a partir de una descripción",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Después de la restauración de la versión, el borrador actual será sobrescrito.",
"versionHistory.title": "Versiones",
"workflowGenerator.applied": "Aplicado",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "No se pudo aplicar el flujo de trabajo",
"workflowGenerator.applyToCurrent": "Aplicar al borrador actual",
"workflowGenerator.applyToNew": "Crear nueva aplicación",
"workflowGenerator.cancel": "Cancelar",
"workflowGenerator.description": "Describe lo que quieres que haga el flujo de trabajo. Elija un modelo, escriba una instrucción y obtenga una vista previa del gráfico generado antes de aplicarlo a Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Descartar",
"workflowGenerator.errors.DANGLING_EDGE": "El flujo de trabajo generado tiene un borde que apunta a un nodo que no existe. Inténtalo de nuevo o mejora tus instrucciones.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "El flujo de trabajo generado contiene IDs de nodo duplicados. Intenta regenerarlo.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "El flujo de trabajo hace referencia a una herramienta que no está instalada para este espacio de trabajo. Instálelo desde la página Herramientas o refine sus instrucciones.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Un nodo en el flujo de trabajo generado hace referencia a una variable que no está declarada en sentido ascendente. Intenta regenerarte.",
"workflowGenerator.errors.apply_failed_orphan": "No pudimos terminar de crear la aplicación. Es posible que se haya dejado un borrador vacío en su lista de aplicaciones; elimínelo manualmente.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "El borrador del flujo de trabajo se editó en otra pestaña. Vuelva a cargar para recoger esos cambios, luego intente aplicarlos nuevamente.",
"workflowGenerator.errors.hash_collision_title": "El espacio de trabajo fue editado en otro lugar",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "La generación tomó demasiado tiempo. Es posible que el modelo sea lento o no esté disponible; inténtalo de nuevo.",
"workflowGenerator.examples.chatflow.support": "Bot de atención al cliente respaldado por una base de conocimientos",
"workflowGenerator.examples.chatflow.triage": "Clasifique las preguntas entrantes y diríjalas a un mensaje especializado",
"workflowGenerator.examples.chatflow.tutor": "Tutor multilingüe que explica paso a paso",
"workflowGenerator.examples.label": "Prueba uno de estos",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Obtenga problemas de GitHub y clasifíquelos",
"workflowGenerator.examples.workflow.rag": "Consulta de la base de conocimientos, luego formatee la respuesta como Markdown",
"workflowGenerator.examples.workflow.summarize": "Resumir una URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "No se pudo cargar el borrador actual; se generará desde cero.",
"workflowGenerator.refineInstructionPlaceholder": "Describe el cambio, p.e. agregue un paso de traducción, cambie a una herramienta, agregue manejo de errores.",
"workflowGenerator.refineTitle": "Refinar {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "recargar",
"workflowGenerator.studioApply": "Aplicar",
"workflowGenerator.studioButton": "generar",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "هنوز برنامه‌ای وجود ندارد",
"gotoAnything.actions.accountDesc": "به صفحه حساب کاربری بروید",
"gotoAnything.actions.communityDesc": "جامعه دیسکورد باز",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "یک گردش کار یا جریان گفتگو ایجاد شده توسط هوش مصنوعی ایجاد کنید",
"gotoAnything.actions.createChatflow": "جریان چت",
"gotoAnything.actions.createChatflowDesc": "یک برنامه chatflow (چت پیشرفته) از توضیحات ایجاد کنید",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "پس از بازیابی نسخه، پیش‌نویس فعلی بازنویسی خواهد شد.",
"versionHistory.title": "تاریخچه نسخه‌ها",
"workflowGenerator.applied": "اعمال شد",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "اعمال گردش کار انجام نشد",
"workflowGenerator.applyToCurrent": "برای پیش نویس فعلی اعمال شود",
"workflowGenerator.applyToNew": "برنامه جدید ایجاد کنید",
"workflowGenerator.cancel": "لغو کنید",
"workflowGenerator.description": "آنچه را که می خواهید گردش کار انجام دهد را شرح دهید. یک مدل را انتخاب کنید، یک دستورالعمل بنویسید، و پیش نمایش نمودار تولید شده را قبل از اعمال آن در استودیو مشاهده کنید.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "رد کردن",
"workflowGenerator.errors.DANGLING_EDGE": "گردش کار تولید شده دارای لبه ای است که به گره ای اشاره می کند که وجود ندارد. دوباره امتحان کنید یا دستورالعمل خود را اصلاح کنید.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "گردش‌کار تولیدشده شامل شناسه‌های گره تکراری است. دوباره تولید کنید.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "گردش کار به ابزاری اشاره می کند که برای این فضای کاری نصب نشده است. آن را از صفحه ابزار نصب کنید یا دستورالعمل خود را اصلاح کنید.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "یک گره در گردش کار تولید شده به متغیری ارجاع می دهد که در بالادست اعلام نشده است. بازسازی را امتحان کنید.",
"workflowGenerator.errors.apply_failed_orphan": "ما نتوانستیم ایجاد برنامه را به پایان برسانیم. ممکن است یک پیش نویس خالی در لیست برنامه های شما باقی مانده باشد - لطفاً آن را به صورت دستی حذف کنید.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "پیش نویس گردش کار در برگه دیگری ویرایش شد. دوباره بارگیری کنید تا آن تغییرات را دریافت کنید، سپس دوباره اعمال کنید.",
"workflowGenerator.errors.hash_collision_title": "فضای کاری در جای دیگری ویرایش شد",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "نسل خیلی طول کشید. ممکن است مدل کند باشد یا در دسترس نباشد - دوباره امتحان کنید.",
"workflowGenerator.examples.chatflow.support": "ربات پشتیبانی مشتری با پشتیبانی یک پایگاه دانش",
"workflowGenerator.examples.chatflow.triage": "سوالات دریافتی را تریاژ کنید و به یک اعلان متخصص بروید",
"workflowGenerator.examples.chatflow.tutor": "مدرس چند زبانه که گام به گام توضیح می دهد",
"workflowGenerator.examples.label": "یکی از اینها را امتحان کنید",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "مشکلات GitHub را واکشی کنید و آنها را طبقه بندی کنید",
"workflowGenerator.examples.workflow.rag": "پرس و جو مبتنی بر دانش، سپس پاسخ را به عنوان Markdown قالب بندی کنید",
"workflowGenerator.examples.workflow.summarize": "یک URL را خلاصه کنید",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "بارگیری پیش‌نویس فعلی ممکن نشد — در عوض از ابتدا تولید می‌شود.",
"workflowGenerator.refineInstructionPlaceholder": "تغییر را توصیف کنید - به عنوان مثال یک مرحله ترجمه اضافه کنید، به یک ابزار بروید، مدیریت خطا را اضافه کنید.",
"workflowGenerator.refineTitle": "اصلاح {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "بارگذاری مجدد",
"workflowGenerator.studioApply": "درخواست کنید",
"workflowGenerator.studioButton": "ایجاد کنید",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Aucune application pour le moment",
"gotoAnything.actions.accountDesc": "Accédez à la page de compte",
"gotoAnything.actions.communityDesc": "Ouvrir la communauté Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Créez un flux de travail ou un chatflow généré par l'IA",
"gotoAnything.actions.createChatflow": "Flux de discussion",
"gotoAnything.actions.createChatflowDesc": "Générer une application chatflow (chat avancé) à partir d'une description",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Après la restauration de la version, le brouillon actuel sera écrasé.",
"versionHistory.title": "Versions",
"workflowGenerator.applied": "Appliqué",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Échec de l'application du flux de travail",
"workflowGenerator.applyToCurrent": "Appliquer au brouillon actuel",
"workflowGenerator.applyToNew": "Créer une nouvelle application",
"workflowGenerator.cancel": "Annuler",
"workflowGenerator.description": "Décrivez ce que vous souhaitez que le flux de travail fasse. Choisissez un modèle, rédigez une instruction et prévisualisez le graphique généré avant de l'appliquer à Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Rejeter",
"workflowGenerator.errors.DANGLING_EDGE": "Le flux de travail généré comporte un bord pointant vers un nœud qui n'existe pas. Réessayez ou affinez vos instructions.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Le workflow généré contient des ID de nœud en double. Essayez de le régénérer.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "Le workflow fait référence à un outil qui n'est pas installé pour cet espace de travail. Installez-le à partir de la page Outils ou affinez vos instructions.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Un nœud dans le workflow généré fait référence à une variable qui n'est pas déclarée en amont. Essayez de vous régénérer.",
"workflowGenerator.errors.apply_failed_orphan": "Nous n'avons pas besoin de terminer la création de l'application. Un brouillon vide a peut-être été laissé dans votre liste d'applications : veuillez le supprimer manuellement.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Le brouillon du workflow a été modifié dans un autre onglet. Actualisez pour récupérer ces modifications, puis réessayez de postuler.",
"workflowGenerator.errors.hash_collision_title": "L'espace de travail a été modifié ailleurs",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "La génération a pris trop de temps. Le modèle est peut-être lent ou indisponible : réessayez.",
"workflowGenerator.examples.chatflow.support": "Bot de support client soutenu par une base de connaissances",
"workflowGenerator.examples.chatflow.triage": "Triez les questions entrantes et acheminez-les vers une invite spécialisée",
"workflowGenerator.examples.chatflow.tutor": "Tuteur multilingue qui explique étape par étape",
"workflowGenerator.examples.label": "Essayez-en un",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Récupérez les problèmes GitHub et classez-les",
"workflowGenerator.examples.workflow.rag": "Requête de la base de connaissances, puis formatez la réponse en Markdown",
"workflowGenerator.examples.workflow.summarize": "Résumer une URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Impossible de charger le brouillon actuel — génération à partir de zéro à la place.",
"workflowGenerator.refineInstructionPlaceholder": "Décrivez le changement — par ex. ajouter une étape de traduction, passer un outil, ajouter une gestion des erreurs.",
"workflowGenerator.refineTitle": "Affiner {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Recharger",
"workflowGenerator.studioApply": "Postuler",
"workflowGenerator.studioButton": "Générer",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "अभी कोई ऐप नहीं है",
"gotoAnything.actions.accountDesc": "खाता पृष्ठ पर जाएं",
"gotoAnything.actions.communityDesc": "ओपन डिस्कॉर्ड समुदाय",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "एआई-जनरेटेड वर्कफ़्लो या चैटफ़्लो बनाएं",
"gotoAnything.actions.createChatflow": "चैटफ़्लो",
"gotoAnything.actions.createChatflowDesc": "विवरण से एक चैटफ़्लो (उन्नत चैट) ऐप बनाएं",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "संस्करण पुनर्स्थापन के बाद, वर्तमान ड्राफ्ट अधिलेखित किया जाएगा।",
"versionHistory.title": "संस्करण",
"workflowGenerator.applied": "लागू",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "वर्कफ़्लो लागू करने में विफल",
"workflowGenerator.applyToCurrent": "वर्तमान ड्राफ्ट पर लागू करें",
"workflowGenerator.applyToNew": "नया ऐप बनाएं",
"workflowGenerator.cancel": "रद्द करें",
"workflowGenerator.description": "वर्णन करें कि आप वर्कफ़्लो से क्या कराना चाहते हैं. एक मॉडल चुनें, एक निर्देश लिखें और स्टूडियो पर लागू करने से पहले जेनरेट किए गए ग्राफ़ का पूर्वावलोकन करें।",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "ख़ारिज करें",
"workflowGenerator.errors.DANGLING_EDGE": "जेनरेट किए गए वर्कफ़्लो में एक किनारा उस नोड की ओर इशारा करता है जो मौजूद नहीं है। पुनः प्रयास करें या अपने निर्देश को परिष्कृत करें।",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "जनरेट किए गए वर्कफ़्लो में डुप्लिकेट नोड ID हैं। फिर से जनरेट करने का प्रयास करें।",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "व्रकफ्लो वन एसे टूल का संभोग देयता है जो इस व्रत की कसौटी पर आधारित है। इसटूलपेज सेन्सॉल्ट करं याअपना निरोध को परिक्षेत्र कृति करं।",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "जेनरेट किए गए वर्कफ़्लो में एक नोड एक वेरिएबल को संदर्भित करता है जिसे अपस्ट्रीम घोषित नहीं किया गया है। पुन: उत्पन्न करने का प्रयास करें.",
"workflowGenerator.errors.apply_failed_orphan": "हम ऐप बनाना पूरा नहीं कर सके. हो सकता है कि आपकी ऐप्स सूची में एक खाली ड्राफ्ट छोड़ा गया हो - कृपया इसे मैन्युअल रूप से हटा दें।",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "वर्कफ़्लो ड्राफ्ट को दूसरे टैब में संपादित किया गया था। उन परिवर्तनों को लेने के लिए पुनः लोड करें, फिर दोबारा लागू करने का प्रयास करें।",
"workflowGenerator.errors.hash_collision_title": "कार्यक्षेत्र को अन्यत्र संपादित किया गया था",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "जनरेशन में बहुत समय लगरहा है। मोडल धीमा अनूपबत हो सकता है - पूनः प्रियास करं।",
"workflowGenerator.examples.chatflow.support": "ग्राहक-सहायता बॉट ज्ञान आधार द्वारा समर्थित है",
"workflowGenerator.examples.chatflow.triage": "आने वाले प्रश्नों का परीक्षण करें और किसी विशेषज्ञ के पास भेजें",
"workflowGenerator.examples.chatflow.tutor": "बहुभाषी ट्यूटर जो चरण दर चरण समझाता है",
"workflowGenerator.examples.label": "इनमें से किसी एक को आज़माएं",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "GitHub मुद्दे प्राप्त करें और उन्हें वर्गीकृत करें",
"workflowGenerator.examples.workflow.rag": "नॉलेज-बेस क्वेरी, फिर उत्तर को मार्कडाउन के रूप में प्रारूपित करें",
"workflowGenerator.examples.workflow.summarize": "किसी URL को सारांशित करें",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "वर्तमान ड्राफ़्ट लोड नहीं हो सका — इसके बजाय शुरुआत से जनरेट किया जा रहा है।",
"workflowGenerator.refineInstructionPlaceholder": "परिवर्तन का वर्णन करें - उदा. अनुवाद चरण जोड़ें, टूल पर स्विच करें, त्रुटि प्रबंधन जोड़ें।",
"workflowGenerator.refineTitle": "परिष्कृत करें {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "पुनः लोड करें",
"workflowGenerator.studioApply": "ऐसा लगता है कि",
"workflowGenerator.studioButton": "उत्पन्न करें",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Belum ada aplikasi",
"gotoAnything.actions.accountDesc": "Arahkan ke halaman akun",
"gotoAnything.actions.communityDesc": "Buka komunitas Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Buat alur kerja atau alur obrolan yang dihasilkan AI",
"gotoAnything.actions.createChatflow": "Alur obrolan",
"gotoAnything.actions.createChatflowDesc": "Hasilkan aplikasi chatflow (obrolan lanjutan) dari deskripsi",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Setelah pemulihan versi, draf saat ini akan ditimpa.",
"versionHistory.title": "Versi",
"workflowGenerator.applied": "Diterapkan",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Gagal menerapkan alur kerja",
"workflowGenerator.applyToCurrent": "Terapkan ke draf saat ini",
"workflowGenerator.applyToNew": "Buat aplikasi baru",
"workflowGenerator.cancel": "Batalkan",
"workflowGenerator.description": "Jelaskan apa yang Anda ingin alur kerja lakukan. Pilih model, tulis instruksi, dan pratinjau grafik yang dihasilkan sebelum menerapkannya ke Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Singkirkan",
"workflowGenerator.errors.DANGLING_EDGE": "Alur kerja yang dihasilkan memiliki tepi yang menunjuk pada simpul yang tidak ada. Coba lagi atau perbaiki instruksi Anda.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Alur kerja yang dihasilkan berisi ID node duplikat. Coba buat ulang.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "Alur kerja mereferensikan alat yang tidak diinstal untuk ruang kerja ini. Instal dari halaman Alat atau sempurnakan instruksi Anda.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Sebuah simpul dalam alur kerja yang dihasilkan mereferensikan variabel yang tidak dideklarasikan di bagian hulu. Coba regenerasi.",
"workflowGenerator.errors.apply_failed_orphan": "Kami tidak dapat menyelesaikan pembuatan aplikasi. Draf kosong mungkin tertinggal di daftar aplikasi Anda — harap hapus secara manual.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Draf alur kerja telah diedit di tab lain. Muat ulang untuk mengambil perubahan tersebut, lalu coba terapkan lagi.",
"workflowGenerator.errors.hash_collision_title": "Ruang kerja telah diedit di tempat lain",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Pembuatannya memakan waktu terlalu lama. Modelnya mungkin lambat atau tidak tersedia — coba lagi.",
"workflowGenerator.examples.chatflow.support": "Bot dukungan pelanggan didukung oleh basis pengetahuan",
"workflowGenerator.examples.chatflow.triage": "Triase pertanyaan masuk dan arahkan ke perintah spesialis",
"workflowGenerator.examples.chatflow.tutor": "Tutor multi-bahasa yang menjelaskan langkah demi langkah",
"workflowGenerator.examples.label": "Cobalah salah satunya",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Ambil masalah GitHub dan klasifikasikan",
"workflowGenerator.examples.workflow.rag": "Kueri basis pengetahuan, lalu format jawabannya sebagai Penurunan harga",
"workflowGenerator.examples.workflow.summarize": "Ringkaslah sebuah URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Tidak dapat memuat draf saat ini — membuat dari awal sebagai gantinya.",
"workflowGenerator.refineInstructionPlaceholder": "Jelaskan perubahannya — mis. tambahkan langkah terjemahan, beralih ke alat, tambahkan penanganan kesalahan.",
"workflowGenerator.refineTitle": "Sempurnakan {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Muat ulang",
"workflowGenerator.studioApply": "Terapkan",
"workflowGenerator.studioButton": "Hasilkan",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Ancora nessuna app",
"gotoAnything.actions.accountDesc": "Vai alla pagina dell'account",
"gotoAnything.actions.communityDesc": "Apri la community di Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Crea un flusso di lavoro o un flusso di chat generato dall'intelligenza artificiale",
"gotoAnything.actions.createChatflow": "Flusso di chat",
"gotoAnything.actions.createChatflowDesc": "Genera un'app del flusso di chat (chat avanzata) da una descrizione",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Dopo il ripristino della versione, la bozza attuale verrà sovrascritta.",
"versionHistory.title": "Versioni",
"workflowGenerator.applied": "Applicato",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Impossibile applicare il flusso di lavoro",
"workflowGenerator.applyToCurrent": "Applica alla bozza corrente",
"workflowGenerator.applyToNew": "Crea una nuova app",
"workflowGenerator.cancel": "Annulla",
"workflowGenerator.description": "Descrivi cosa vuoi che faccia il flusso di lavoro. Scegli un modello, scrivi un'istruzione e visualizza l'anteprima del grafico generato prima di applicarlo a Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Ignora",
"workflowGenerator.errors.DANGLING_EDGE": "Il flusso di lavoro generato ha un bordo che punta a un nodo che non esiste. Riprova o perfeziona le tue istruzioni.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Il workflow generato contiene ID di nodo duplicati. Prova a rigenerarlo.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "Il flusso di lavoro fa riferimento a uno strumento che non è installato per questa area di lavoro. Installalo dalla pagina Strumenti o perfeziona le tue istruzioni.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Un nodo nel flusso di lavoro generato fa riferimento a una variabile che non è dichiarata a monte. Prova a rigenerarti.",
"workflowGenerator.errors.apply_failed_orphan": "Non è stato possibile completare la creazione dell'app. Potrebbe essere stata lasciata una bozza vuota nell'elenco delle tue app: rimuovila manualmente.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "La bozza del flusso di lavoro è stata modificata in un'altra scheda. Ricarica per riprendere le modifiche, quindi prova ad applicarle nuovamente.",
"workflowGenerator.errors.hash_collision_title": "L'area di lavoro è stata modificata altrove",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "La generazione ha richiesto troppo tempo. Il modello potrebbe essere lento o non disponibile: riprova.",
"workflowGenerator.examples.chatflow.support": "Bot di assistenza clienti supportato da una knowledge base",
"workflowGenerator.examples.chatflow.triage": "Valuta le domande in arrivo e indirizzale a una richiesta specialistica",
"workflowGenerator.examples.chatflow.tutor": "Tutor multilingue che spiega passo dopo passo",
"workflowGenerator.examples.label": "Prova uno di questi",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Recupera i problemi di GitHub e classificali",
"workflowGenerator.examples.workflow.rag": "Query della knowledge base, quindi formatta la risposta come Markdown",
"workflowGenerator.examples.workflow.summarize": "Riepilogare un URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Impossibile caricare la bozza corrente: la generazione partirà da zero.",
"workflowGenerator.refineInstructionPlaceholder": "Descrivi il cambiamento – ad es. aggiungere una fase di traduzione, passare a uno strumento, aggiungere la gestione degli errori.",
"workflowGenerator.refineTitle": "Perfeziona {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Ricarica",
"workflowGenerator.studioApply": "Applicare",
"workflowGenerator.studioButton": "Genera",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "最初のアプリを作成",
"gotoAnything.actions.accountDesc": "アカウントページに移動する",
"gotoAnything.actions.communityDesc": "オープンDiscordコミュニティ",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "AI が生成したワークフローまたはチャットフローを作成する",
"gotoAnything.actions.createChatflow": "チャットフロー",
"gotoAnything.actions.createChatflowDesc": "説明からチャットフロー (高度なチャット) アプリを生成する",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "バージョンを復元すると、現在の下書きが上書きされます",
"versionHistory.title": "バージョン",
"workflowGenerator.applied": "適用済み",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "ワークフローの適用に失敗しました",
"workflowGenerator.applyToCurrent": "現在のドラフトに適用",
"workflowGenerator.applyToNew": "新しいアプリを作成する",
"workflowGenerator.cancel": "キャンセル",
"workflowGenerator.description": "ワークフローで実行したいことを説明します。モデルを選択し、命令を記述し、生成されたグラフを Studio に適用する前にプレビューします。",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "解雇する",
"workflowGenerator.errors.DANGLING_EDGE": "生成されたワークフローには、存在しないノードを指すエッジがあります。もう一度お試しいただくか、手順を絞り込んでください。",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "生成されたワークフローに重複するノード ID が含まれています。再生成してください。",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "ワークフローは、このワークスペースにインストールされていないツールを参照します。ツールページからインストールするか、手順を絞り込みます。",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "生成されたワークフロー内のノードは、アップストリームで宣言されていない変数を参照しています。再生成してみてください。",
"workflowGenerator.errors.apply_failed_orphan": "アプリの作成を完了できませんでした。アプリリストに空の下書きが残っている可能性があります。手動で削除してください。",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "ワークフローの下書きが別のタブで編集されました。再読み込みして変更を反映させ、再度適用してください。",
"workflowGenerator.errors.hash_collision_title": "ワークスペースが他の場所で編集されました",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "生成に時間がかかりすぎました。モデルが遅いか、利用できない可能性があります。もう一度お試しください。",
"workflowGenerator.examples.chatflow.support": "ナレッジベースに裏付けられたカスタマーサポートボット",
"workflowGenerator.examples.chatflow.triage": "受信した質問をトリアージし、スペシャリストのプロンプトにエスカレートします",
"workflowGenerator.examples.chatflow.tutor": "段階的に説明する多言語講師",
"workflowGenerator.examples.label": "見事だ いや ローレルに話したんだ",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "GitHub の問題を取得して分類する",
"workflowGenerator.examples.workflow.rag": "ナレッジベースのクエリ。回答をマークダウン形式でフォーマットします。",
"workflowGenerator.examples.workflow.summarize": "URLを要約する",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "現在のドラフトを読み込めませんでした。最初から生成します。",
"workflowGenerator.refineInstructionPlaceholder": "変更点を説明してください。たとえば、翻訳ステップの追加、ツールへの切り替え、エラー処理の追加などです。",
"workflowGenerator.refineTitle": "{{mode}} を絞り込む",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "リロード",
"workflowGenerator.studioApply": "申し込む",
"workflowGenerator.studioButton": "生成する",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "아직 앱이 없습니다",
"gotoAnything.actions.accountDesc": "계정 페이지로 이동",
"gotoAnything.actions.communityDesc": "오픈 디스코드 커뮤니티",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "AI 생성 워크플로 또는 채팅 흐름 만들기",
"gotoAnything.actions.createChatflow": "챗플로우",
"gotoAnything.actions.createChatflowDesc": "설명에서 chatflow (№ 급 채팅) 앱 생성",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "버전 복원 후 현재 초안이 덮어쓰여질 것입니다.",
"versionHistory.title": "버전 기록",
"workflowGenerator.applied": "적용",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "워크플로우를 적용하지 못했습니다.",
"workflowGenerator.applyToCurrent": "현재 초안에 적용",
"workflowGenerator.applyToNew": "새 앱 만들기",
"workflowGenerator.cancel": "취소",
"workflowGenerator.description": "워크플로에서 수행하생성된 그래프를 스튜디오는 작업을 설명하세요 모델을 택하 지침을 작성하(에Studio용하기)에 미리 봅니다.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "닫기",
"workflowGenerator.errors.DANGLING_EDGE": "생성된 워크플로에는 존재하지 않는 노드를 가리키는 에지가 있습니다. 다시 시도하거나 지침을 수정하세요.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "생성된 워크플로에 중복된 노드 ID가 있습니다. 다시 생성해 보세요.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "워크플로가 이 작업 영역에 설치되지 않은 도구를 참조합니다. 도구 페이지에서 설치하거나 지침을 구체화하세요.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "생성된 워크플로의 노드는 업스트림으로 선언되지 않은 변수를 참조합니다. 다시 생성해 보세요.",
"workflowGenerator.errors.apply_failed_orphan": "앱 생성을 완료할 수 없습니다. 앱 목록에 비어 있는 초안이 남아 있을 수 있습니다. 수동으로 삭제하세요.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "워크플로 초안이 다른 탭에서 편집되었습니다. 새로고침하여 변경사항을 선택한 후 다시 적용하세요.",
"workflowGenerator.errors.hash_collision_title": "작업공간이 다른 곳에서 편집되었습니다.",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "세대가 너무 오래 걸렸습니다. 모델이 느리거나 사용할 수 없을 수 있습니다. 다시 시도하십시오.",
"workflowGenerator.examples.chatflow.support": "기술 자료가 뒷받침하는 고객 지원 봇",
"workflowGenerator.examples.chatflow.triage": "들어오는 질문을 분류하고 담당자 프롬프트로 전달합니다.",
"workflowGenerator.examples.chatflow.tutor": "단계별로 설명해주는 다국어 튜터",
"workflowGenerator.examples.label": "다음 중 하나를 시도해 보세요.",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "GitHub 문(를 가기트허브)와 분류합니다.",
"workflowGenerator.examples.workflow.rag": "지식 기반 쿼리 후 답변 형식을 마크다운 (으로 지MARKDOWN)",
"workflowGenerator.examples.workflow.summarize": "URL 요약",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "현재 초안을 불러올 수 없어 처음부터 생성합니다.",
"workflowGenerator.refineInstructionPlaceholder": "변경 사항 설명 — 예: 번역 단계 추가, 도구로 전환, 오류 처리 추가.",
"workflowGenerator.refineTitle": "{{mode}} 세부 조정",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "재장전",
"workflowGenerator.studioApply": "용",
"workflowGenerator.studioButton": "생성",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Nog geen app",
"gotoAnything.actions.accountDesc": "Navigate to account page",
"gotoAnything.actions.communityDesc": "Open Discord community",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Creëer een door AI gegenereerde workflow of chatflow",
"gotoAnything.actions.createChatflow": "Chatstroom",
"gotoAnything.actions.createChatflowDesc": "Genereer een chatflow-app (geavanceerde chat) op basis van een beschrijving",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "After version restoration, the current draft will be overwritten.",
"versionHistory.title": "Versions",
"workflowGenerator.applied": "Toegepast",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Kan werkstroom niet toepassen",
"workflowGenerator.applyToCurrent": "Toepassen op huidig concept",
"workflowGenerator.applyToNew": "Nieuwe app maken",
"workflowGenerator.cancel": "Annuleer",
"workflowGenerator.description": "Beschrijf wat u wilt dat de workflow doet. Kies een model, schrijf een instructie en bekijk een voorbeeld van de gegenereerde grafiek voordat u deze in Studio toepast.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Negeren",
"workflowGenerator.errors.DANGLING_EDGE": "De gegenereerde werkstroom heeft een rand die naar een knooppunt wijst dat niet bestaat. Probeer het opnieuw of verfijn uw instructie.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "De gegenereerde workflow bevat dubbele node-ID's. Probeer opnieuw te genereren.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "De werkstroom verwijst naar een tool die niet voor deze werkruimte is geïnstalleerd. Installeer het vanaf de pagina Tools of verfijn uw instructie.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Een knooppunt in de gegenereerde werkstroom verwijst naar een variabele die niet stroomopwaarts is gedeclareerd. Probeer te regenereren.",
"workflowGenerator.errors.apply_failed_orphan": "We konden het maken van de app niet voltooien. Mogelijk is er een leeg concept in uw lijst met apps achtergebleven. Verwijder dit handmatig.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Het werkstroomconcept is op een ander tabblad bewerkt. Laad opnieuw om deze wijzigingen op te pikken en probeer vervolgens opnieuw te solliciteren.",
"workflowGenerator.errors.hash_collision_title": "De werkruimte is elders bewerkt",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Het genereren duurde te lang. Het model is mogelijk traag of niet beschikbaar. Probeer het opnieuw.",
"workflowGenerator.examples.chatflow.support": "Klantenondersteuningsbot ondersteund door een kennisbank",
"workflowGenerator.examples.chatflow.triage": "Triageer inkomende vragen en stuur ze door naar een specialist",
"workflowGenerator.examples.chatflow.tutor": "Meertalige docent die stap voor stap uitleg geeft",
"workflowGenerator.examples.label": "Probeer een van deze",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Haal GitHub-problemen op en classificeer ze",
"workflowGenerator.examples.workflow.rag": "Query uit de kennisbank en formatteer het antwoord vervolgens als Markdown",
"workflowGenerator.examples.workflow.summarize": "Een URL samenvatten",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Kan het huidige concept niet laden — er wordt vanaf nul gegenereerd.",
"workflowGenerator.refineInstructionPlaceholder": "Beschrijf de verandering — b.v. voeg een vertaalstap toe, schakel over naar een tool, voeg foutafhandeling toe.",
"workflowGenerator.refineTitle": "Verfijn {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Herladen",
"workflowGenerator.studioApply": "Toepassen",
"workflowGenerator.studioButton": "Genereer",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Nie ma jeszcze aplikacji",
"gotoAnything.actions.accountDesc": "Przejdź do strony konta",
"gotoAnything.actions.communityDesc": "Otwarta społeczność Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Utwórz przepływ pracy lub czat generowany przez sztuczną inteligencję",
"gotoAnything.actions.createChatflow": "Przepływ czatu",
"gotoAnything.actions.createChatflowDesc": "Wygeneruj aplikację Chatflow (czat zaawansowany) na podstawie opisu",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Po przywróceniu wersji bieżący szkic zostanie nadpisany.",
"versionHistory.title": "Wersje",
"workflowGenerator.applied": "Zastosowano",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Nie udało się zastosować przepływu pracy",
"workflowGenerator.applyToCurrent": "Zastosuj do bieżącej wersji roboczej",
"workflowGenerator.applyToNew": "Utwórz nową aplikację",
"workflowGenerator.cancel": "Anuluj",
"workflowGenerator.description": "Opisz, co chcesz zrobić w ramach przepływu pracy. Wybierz model, napisz instrukcję i wyświetl podgląd wygenerowanego wykresu przed zastosowaniem go w Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Odrzuć",
"workflowGenerator.errors.DANGLING_EDGE": "Wygenerowany przepływ pracy ma krawędź wskazującą na węzeł, który nie istnieje. Spróbuj ponownie lub doprecyzuj instrukcję.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Wygenerowany przepływ pracy zawiera zduplikowane identyfikatory węzłów. Spróbuj wygenerować ponownie.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "Przepływ pracy odwołuje się do narzędzia, które nie jest zainstalowane dla tego obszaru roboczego. Zainstaluj go ze strony Narzędzia lub doprecyzuj instrukcję.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Węzeł w wygenerowanym przepływie pracy odwołuje się do zmiennej, która nie jest zadeklarowana wcześniej. Spróbuj regeneracji.",
"workflowGenerator.errors.apply_failed_orphan": "Nie mogliśmy dokończyć tworzenia aplikacji. Na liście aplikacji mogła pozostać pusta wersja robocza — usuń ją ręcznie.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Wersja robocza przepływu pracy została edytowana w innej zakładce. Załaduj ponownie, aby zastosować zmiany, a następnie spróbuj zastosować ponownie.",
"workflowGenerator.errors.hash_collision_title": "Obszar roboczy został edytowany w innym miejscu",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Generowanie trwało zbyt długo. Model może działać wolno lub być niedostępny — spróbuj ponownie.",
"workflowGenerator.examples.chatflow.support": "Bot obsługi klienta wspierany bazą wiedzy",
"workflowGenerator.examples.chatflow.triage": "Segreguj przychodzące pytania i kieruj do podpowiedzi specjalisty",
"workflowGenerator.examples.chatflow.tutor": "Wielojęzyczny korepetytor, który wyjaśnia krok po kroku",
"workflowGenerator.examples.label": "Wypróbuj jeden z nich",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Pobieraj problemy z GitHubem i klasyfikuj je",
"workflowGenerator.examples.workflow.rag": "Zapytanie do bazy wiedzy, a następnie sformatuj odpowiedź jako Markdown",
"workflowGenerator.examples.workflow.summarize": "Podsumuj adres URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Nie udało się wczytać bieżącego szkicu — generowanie od podstaw.",
"workflowGenerator.refineInstructionPlaceholder": "Opisz zmianę – np. dodaj krok tłumaczenia, przejdź do narzędzia, dodaj obsługę błędów.",
"workflowGenerator.refineTitle": "Doprecyzuj {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Załaduj ponownie",
"workflowGenerator.studioApply": "Zastosuj",
"workflowGenerator.studioButton": "Wygeneruj",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Ainda não há apps",
"gotoAnything.actions.accountDesc": "Navegue até a página da conta",
"gotoAnything.actions.communityDesc": "Comunidade do Discord aberta",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Crie um fluxo de trabalho ou fluxo de chat gerado por IA",
"gotoAnything.actions.createChatflow": "Fluxo de bate-papo",
"gotoAnything.actions.createChatflowDesc": "Gere um aplicativo chatflow (chat avançado) a partir de uma descrição",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Após a restauração da versão, o rascunho atual será substituído.",
"versionHistory.title": "Versões",
"workflowGenerator.applied": "Aplicado",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Falha ao aplicar o fluxo de trabalho",
"workflowGenerator.applyToCurrent": "Aplicar ao rascunho atual",
"workflowGenerator.applyToNew": "Criar novo aplicativo",
"workflowGenerator.cancel": "Cancelar",
"workflowGenerator.description": "Descreva o que você deseja que o fluxo de trabalho faça. Escolha um modelo, escreva uma instrução e visualize o gráfico gerado antes de aplicá-lo ao Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Dispensar",
"workflowGenerator.errors.DANGLING_EDGE": "O fluxo de trabalho gerado tem uma borda apontando para um nó que não existe. Tente novamente ou refine suas instruções.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "O fluxo de trabalho gerado contém IDs de nó duplicados. Tente gerar novamente.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "O fluxo de trabalho faz referência a uma ferramenta que não está instalada para este espaço de trabalho. Instale-o na página Ferramentas ou refine suas instruções.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Um nó no fluxo de trabalho gerado faz referência a uma variável que não é declarada upstream. Tente regenerar.",
"workflowGenerator.errors.apply_failed_orphan": "Não conseguimos terminar de criar o aplicativo. Um rascunho vazio pode ter sido deixado na sua lista de aplicativos. Remova-o manualmente.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "O rascunho do fluxo de trabalho foi editado em outra guia. Recarregue para pegar essas alterações e tente aplicar novamente.",
"workflowGenerator.errors.hash_collision_title": "O espaço de trabalho foi editado em outro lugar",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "A geração demorou muito. O modelo pode estar lento ou indisponível. Tente novamente.",
"workflowGenerator.examples.chatflow.support": "Bot de suporte ao cliente apoiado por uma base de conhecimento",
"workflowGenerator.examples.chatflow.triage": "Triagem de perguntas recebidas e encaminhamento para um especialista",
"workflowGenerator.examples.chatflow.tutor": "Tutor multilíngue que explica passo a passo",
"workflowGenerator.examples.label": "Experimente um destes",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Busque problemas do GitHub e classifique-os",
"workflowGenerator.examples.workflow.rag": "Consulta na base de conhecimento e formate a resposta como Markdown",
"workflowGenerator.examples.workflow.summarize": "Resuma um URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Não foi possível carregar o rascunho atual — gerando do zero.",
"workflowGenerator.refineInstructionPlaceholder": "Descreva a mudança - por ex. adicione uma etapa de tradução, mude para uma ferramenta, adicione tratamento de erros.",
"workflowGenerator.refineTitle": "Refinar {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Recarregar",
"workflowGenerator.studioApply": "Aplicar",
"workflowGenerator.studioButton": "Gerar",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Încă nu există aplicații",
"gotoAnything.actions.accountDesc": "Navigați la pagina de cont",
"gotoAnything.actions.communityDesc": "Deschide comunitatea Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Creați un flux de lucru sau un flux de chat generat de AI",
"gotoAnything.actions.createChatflow": "Flux de chat",
"gotoAnything.actions.createChatflowDesc": "Generați o aplicație chatflow (chat avansat) dintr-o descriere",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "După restaurarea versiunii, proiectul actual va fi suprascris.",
"versionHistory.title": "Versiuni",
"workflowGenerator.applied": "Aplicat",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Nu s-a putut aplica fluxul de lucru",
"workflowGenerator.applyToCurrent": "Aplicați la schița curentă",
"workflowGenerator.applyToNew": "Creați o nouă aplicație",
"workflowGenerator.cancel": "Anulează",
"workflowGenerator.description": "Descrieți ce doriți să facă fluxul de lucru. Alegeți un model, scrieți o instrucțiune și previzualizați graficul generat înainte de a-l aplica în Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Respingeți",
"workflowGenerator.errors.DANGLING_EDGE": "Fluxul de lucru generat are o margine îndreptată către un nod care nu există. Încercați din nou sau îmbunătățiți instrucțiunile.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Fluxul de lucru generat conține ID-uri de nod duplicate. Încercați să îl regenerați.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "Fluxul de lucru face referire la un instrument care nu este instalat pentru acest spațiu de lucru. Instalați-l din pagina Instrumente sau rafinați instrucțiunile.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Un nod din fluxul de lucru generat face referire la o variabilă care nu este declarată în amonte. Încearcă să te regenerezi.",
"workflowGenerator.errors.apply_failed_orphan": "Nu am putut termina de creat aplicația. Este posibil să fi fost lăsată o schiță goală în lista de aplicații - vă rugăm să o eliminați manual.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Schița fluxului de lucru a fost editată într-o altă filă. Reîncărcați pentru a prelua acele modificări, apoi încercați să aplicați din nou.",
"workflowGenerator.errors.hash_collision_title": "Spațiul de lucru a fost editat în altă parte",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Generația a durat prea mult. Modelul poate fi lent sau indisponibil - încercați din nou.",
"workflowGenerator.examples.chatflow.support": "Bot de asistență pentru clienți susținut de o bază de cunoștințe",
"workflowGenerator.examples.chatflow.triage": "Trimiteți întrebările primite și direcționați către un specialist",
"workflowGenerator.examples.chatflow.tutor": "Tutor în mai multe limbi care explică pas cu pas",
"workflowGenerator.examples.label": "Încearcă una dintre acestea",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Preluați problemele GitHub și clasificați-le",
"workflowGenerator.examples.workflow.rag": "Interogare din baza de cunoștințe, apoi formatați răspunsul ca Markdown",
"workflowGenerator.examples.workflow.summarize": "Rezumați o adresă URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Schița curentă nu a putut fi încărcată — se generează de la zero.",
"workflowGenerator.refineInstructionPlaceholder": "Descrieți schimbarea - de ex. adăugați un pas de traducere, treceți la un instrument, adăugați gestionarea erorilor.",
"workflowGenerator.refineTitle": "Rafinați {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Reîncărcați",
"workflowGenerator.studioApply": "Aplicați",
"workflowGenerator.studioButton": "Generați",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Приложений пока нет",
"gotoAnything.actions.accountDesc": "Перейдите на страницу учетной записи",
"gotoAnything.actions.communityDesc": "Открытое сообщество Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Создайте рабочий процесс или поток чата, созданный искусственным интеллектом.",
"gotoAnything.actions.createChatflow": "Чат",
"gotoAnything.actions.createChatflowDesc": "Создайте приложение чата (расширенный чат) из описания.",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "После восстановления версии текущий черновик будет перезаписан.",
"versionHistory.title": "Версии",
"workflowGenerator.applied": "Применяемый",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Не удалось применить рабочий процесс.",
"workflowGenerator.applyToCurrent": "Применить к текущему проекту",
"workflowGenerator.applyToNew": "Создать новое приложение",
"workflowGenerator.cancel": "Отмена",
"workflowGenerator.description": "Опишите, что вы хотите, чтобы рабочий процесс делал. Выберите модель, напишите инструкцию и просмотрите созданный график, прежде чем применять его в Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Увольнять",
"workflowGenerator.errors.DANGLING_EDGE": "Сгенерированный рабочий процесс имеет ребро, указывающее на несуществующий узел. Попробуйте еще раз или уточните инструкцию.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Сгенерированный рабочий процесс содержит повторяющиеся ID узлов. Попробуйте сгенерировать заново.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "Рабочий процесс ссылается на инструмент, который не установлен для этой рабочей области. Установите его со страницы «Инструменты» или доработайте инструкцию.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Узел в созданном рабочем процессе ссылается на переменную, которая не объявлена в исходном коде. Попробуйте регенерировать.",
"workflowGenerator.errors.apply_failed_orphan": "Нам не удалось завершить создание приложения. Возможно, в вашем списке приложений остался пустой черновик — удалите его вручную.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Черновик рабочего процесса был отредактирован на другой вкладке. Перезагрузите файл, чтобы применить эти изменения, а затем попробуйте применить его еще раз.",
"workflowGenerator.errors.hash_collision_title": "Рабочая область была отредактирована в другом месте",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Генерация заняла слишком много времени. Возможно, модель работает медленно или недоступна — попробуйте еще раз.",
"workflowGenerator.examples.chatflow.support": "Бот поддержки клиентов, поддерживаемый базой знаний",
"workflowGenerator.examples.chatflow.triage": "Отсортируйте входящие вопросы и направьте их к специалисту",
"workflowGenerator.examples.chatflow.tutor": "Многоязычный репетитор, который объясняет шаг за шагом",
"workflowGenerator.examples.label": "Попробуйте один из этих",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Получите проблемы с GitHub и классифицируйте их.",
"workflowGenerator.examples.workflow.rag": "Запрос к базе знаний, затем отформатируйте ответ в формате Markdown.",
"workflowGenerator.examples.workflow.summarize": "Обобщение URL-адреса",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Не удалось загрузить текущий черновик — генерация начнётся с нуля.",
"workflowGenerator.refineInstructionPlaceholder": "Опишите изменение – например. добавить этап перевода, переключиться на инструмент, добавить обработку ошибок.",
"workflowGenerator.refineTitle": "Уточнить {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Перезагрузить",
"workflowGenerator.studioApply": "Применять",
"workflowGenerator.studioButton": "Генерировать",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Aplikacij še ni",
"gotoAnything.actions.accountDesc": "Pojdite na stran računa",
"gotoAnything.actions.communityDesc": "Odpri Discord skupnost",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Ustvarite potek dela ali potek klepeta, ki ga ustvari AI",
"gotoAnything.actions.createChatflow": "Potek pogovora",
"gotoAnything.actions.createChatflowDesc": "Iz opisa ustvarite aplikacijo chatflow (napredni klepet).",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Po obnovitvi različice bo trenutni osnutek prepisan.",
"versionHistory.title": "Različice",
"workflowGenerator.applied": "Uporabljeno",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Poteka dela ni bilo mogoče uporabiti",
"workflowGenerator.applyToCurrent": "Uporabi za trenutni osnutek",
"workflowGenerator.applyToNew": "Ustvari novo aplikacijo",
"workflowGenerator.cancel": "Prekliči",
"workflowGenerator.description": "Opišite, kaj želite, da poteka delo. Izberite model, napišite navodila in si predoglejte ustvarjeni graf, preden ga uporabite v Studiu.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Odpusti",
"workflowGenerator.errors.DANGLING_EDGE": "Ustvarjeni potek dela ima rob, ki kaže na vozlišče, ki ne obstaja. Poskusite znova ali izboljšajte svoje navodilo.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Ustvarjeni potek dela vsebuje podvojene ID-je vozlišč. Poskusite ga znova ustvariti.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "Potek dela se sklicuje na orodje, ki ni nameščeno za ta delovni prostor. Namestite ga s strani Orodja ali izboljšajte svoje navodilo.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Vozlišče v ustvarjenem delovnem toku se sklicuje na spremenljivko, ki ni deklarirana navzgor. Poskusi z regeneracijo.",
"workflowGenerator.errors.apply_failed_orphan": "Nismo mogli dokončati ustvarjanja aplikacije. Na vašem seznamu aplikacij je morda ostal prazen osnutek – odstranite ga ročno.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Osnutek poteka dela je bil urejen v drugem zavihku. Znova naložite, da prevzamete te spremembe, nato poskusite znova uporabiti.",
"workflowGenerator.errors.hash_collision_title": "Delovni prostor je bil urejen drugje",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Generiranje je trajalo predolgo. Model je morda počasen ali nedosegljiv - poskusite znova.",
"workflowGenerator.examples.chatflow.support": "Bot za podporo strankam, podprt z bazo znanja",
"workflowGenerator.examples.chatflow.triage": "Razvrstite dohodna vprašanja in jih usmerite k pozivu strokovnjaka",
"workflowGenerator.examples.chatflow.tutor": "Večjezični učitelj, ki razlaga korak za korakom",
"workflowGenerator.examples.label": "Poskusite eno od teh",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Pridobite težave GitHub in jih razvrstite",
"workflowGenerator.examples.workflow.rag": "Poizvedba po bazi znanja, nato odgovor oblikujte kot Markdown",
"workflowGenerator.examples.workflow.summarize": "Povzemite URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Trenutnega osnutka ni bilo mogoče naložiti — ustvarjanje bo začeto od začetka.",
"workflowGenerator.refineInstructionPlaceholder": "Opišite spremembo – npr. dodajte korak prevajanja, preklopite na orodje, dodajte obravnavanje napak.",
"workflowGenerator.refineTitle": "Izboljšaj {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Ponovno naloži",
"workflowGenerator.studioApply": "Prijavite se",
"workflowGenerator.studioButton": "Ustvari",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "ยังไม่มีแอป",
"gotoAnything.actions.accountDesc": "ไปที่หน้าบัญชี",
"gotoAnything.actions.communityDesc": "เปิดชุมชน Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "สร้างเวิร์กโฟลว์หรือแชทที่สร้างโดย AI",
"gotoAnything.actions.createChatflow": "แชทโฟลว์",
"gotoAnything.actions.createChatflowDesc": "สร้างแอป Chatflow (แชทขั้นสูง) จากคำอธิบาย",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "หลังจากการกู้คืนเวอร์ชันแล้ว ร่างปัจจุบันจะถูกเขียนทับ.",
"versionHistory.title": "เวอร์ชัน",
"workflowGenerator.applied": "สมัครแล้ว",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "ล้มเหลวในการใช้เวิร์กโฟลว์",
"workflowGenerator.applyToCurrent": "นำไปใช้กับร่างปัจจุบัน",
"workflowGenerator.applyToNew": "สร้างแอปใหม่",
"workflowGenerator.cancel": "ยกเลิก",
"workflowGenerator.description": "อธิบายสิ่งที่คุณต้องการให้เวิร์กโฟลว์ทำ เลือกโมเดล เขียนคำสั่ง และดูกราฟที่สร้างขึ้นก่อนที่จะนำไปใช้กับ Studio",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "อนุญาตให้ออกไป",
"workflowGenerator.errors.DANGLING_EDGE": "เวิร์กโฟลว์ที่สร้างขึ้นมีขอบที่ชี้ไปที่โหนดที่ไม่มีอยู่ ลองอีกครั้งหรือปรับแต่งคำสั่งของคุณ",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "เวิร์กโฟลว์ที่สร้างขึ้นมี ID โหนดซ้ำกัน ลองสร้างใหม่อีกครั้ง",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "เวิร์กโฟลว์อ้างอิงถึงเครื่องมือที่ไม่ได้ติดตั้งสำหรับพื้นที่ทำงานนี้ ติดตั้งจากหน้าเครื่องมือหรือปรับแต่งคำสั่งของคุณ",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "โหนดในเวิร์กโฟลว์ที่สร้างขึ้นอ้างอิงถึงตัวแปรที่ไม่ได้ประกาศอัปสตรีม ลองสร้างใหม่",
"workflowGenerator.errors.apply_failed_orphan": "เราไม่สามารถสร้างแอปให้เสร็จได้ ร่างเปล่าอาจเหลืออยู่ในรายการแอปของคุณ — โปรดลบออกด้วยตนเอง",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "แบบร่างเวิร์กโฟลว์ได้รับการแก้ไขในแท็บอื่น โหลดซ้ำเพื่อรับการเปลี่ยนแปลงเหล่านั้น แล้วลองใช้อีกครั้ง",
"workflowGenerator.errors.hash_collision_title": "พื้นที่ทำงานได้รับการแก้ไขที่อื่น",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "รุ่นใช้เวลานานเกินไป โมเดลอาจช้าหรือไม่พร้อมใช้งาน — ลองอีกครั้ง",
"workflowGenerator.examples.chatflow.support": "บอทสนับสนุนลูกค้าที่ได้รับการสนับสนุนจากฐานความรู้",
"workflowGenerator.examples.chatflow.triage": "คัดแยกคำถามที่เข้ามาและแจ้งไปยังผู้เชี่ยวชาญ",
"workflowGenerator.examples.chatflow.tutor": "ติวเตอร์หลายภาษาที่อธิบายทีละขั้นตอน",
"workflowGenerator.examples.label": "ลองอย่างใดอย่างหนึ่งเหล่านี้",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "ดึงข้อมูลปัญหา GitHub และจัดประเ⦁ ทปัญหาเหล่านั้น",
"workflowGenerator.examples.workflow.rag": "แบบสอบถามฐานความรู้ จากนั้นจัดรูปแบบคำตอบเป็น Markdown",
"workflowGenerator.examples.workflow.summarize": "สรุป URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "ไม่สามารถโหลดฉบับร่างปัจจุบันได้ — จะสร้างใหม่ตั้งแต่ต้นแทน",
"workflowGenerator.refineInstructionPlaceholder": "อธิบายการเปลี่ยนแปลง — เช่น เพิ่มขั้นตอนการแปล เปลี่ยนไปใช้เครื่องมือ เพิ่มการจัดการข้อผิดพลาด",
"workflowGenerator.refineTitle": "ปรับแต่ง {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "โหลดซ้ำ",
"workflowGenerator.studioApply": "นำมาใช้",
"workflowGenerator.studioButton": "สร้าง",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Henüz uygulama yok",
"gotoAnything.actions.accountDesc": "Hesap sayfasına gidin",
"gotoAnything.actions.communityDesc": "Açık Discord topluluğu",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Yapay zeka tarafından oluşturulan bir iş akışı veya sohbet akışı oluşturun",
"gotoAnything.actions.createChatflow": "Sohbet akışı",
"gotoAnything.actions.createChatflowDesc": "Açıklamadan bir sohbet akışı (gelişmiş sohbet) uygulaması oluşturun",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Sürüm geri yüklemeden sonra, mevcut taslak üzerine yazılacak.",
"versionHistory.title": "Sürümler",
"workflowGenerator.applied": "Uygulandı",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "İş akışı uygulanamadı",
"workflowGenerator.applyToCurrent": "Mevcut taslağa uygula",
"workflowGenerator.applyToNew": "Yeni uygulama oluştur",
"workflowGenerator.cancel": "İptal etmek",
"workflowGenerator.description": "İş akışının ne yapmasını istediğinizi açıklayın. Bir model seçin, bir talimat yazın ve oluşturulan grafiği Studio'ya uygulamadan önce önizleyin.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Kapat",
"workflowGenerator.errors.DANGLING_EDGE": "Oluşturulan iş akışının var olmayan bir düğüme işaret eden bir kenarı var. Tekrar deneyin veya talimatınızı geliştirin.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Oluşturulan iş akışı yinelenen düğüm kimlikleri içeriyor. Yeniden oluşturmayı deneyin.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "İş akışı, bu çalışma alanı için yüklü olmayan bir araca başvuruyor. Araçlar sayfasından yükleyin veya talimatlarınızı geliştirin.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Oluşturulan iş akışındaki bir düğüm, yukarı yönde bildirilmemiş bir değişkene başvuruyor. Yenilenmeyi deneyin.",
"workflowGenerator.errors.apply_failed_orphan": "Uygulamayı oluşturmayı tamamlayamadık. Uygulama listenizde boş bir taslak kalmış olabilir; lütfen bunu manuel olarak kaldırın.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "İş akışı taslağı başka bir sekmede düzenlendi. Bu değişiklikleri almak için yeniden yükleyin ve ardından tekrar başvurmayı deneyin.",
"workflowGenerator.errors.hash_collision_title": "Çalışma alanı başka bir yerde düzenlendi",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Nesil çok uzun sürdü. Model yavaş olabilir veya kullanılamıyor olabilir; tekrar deneyin.",
"workflowGenerator.examples.chatflow.support": "Bilgi tabanı tarafından desteklenen müşteri destek botu",
"workflowGenerator.examples.chatflow.triage": "Gelen soruları önceliklendirin ve uzman istemine yönlendirin",
"workflowGenerator.examples.chatflow.tutor": "Adım adım açıklayan çok dilli öğretmen",
"workflowGenerator.examples.label": "Bunlardan birini deneyin",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "GitHub sorunlarını getir ve sınıflandır",
"workflowGenerator.examples.workflow.rag": "Bilgi tabanı sorgusu, ardından yanıtı Markdown olarak biçimlendirin",
"workflowGenerator.examples.workflow.summarize": "Bir URL'yi özetleme",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Mevcut taslak yüklenemedi — bunun yerine sıfırdan oluşturuluyor.",
"workflowGenerator.refineInstructionPlaceholder": "Değişikliği açıklayın - ör. bir çeviri adımı ekleyin, bir araca geçin, hata işleme ekleyin.",
"workflowGenerator.refineTitle": "{{mode}} hassaslaştır",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Yeniden yükle",
"workflowGenerator.studioApply": "Uygula",
"workflowGenerator.studioButton": "Oluştur",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Застосунків ще немає",
"gotoAnything.actions.accountDesc": "Перейдіть на сторінку облікового запису",
"gotoAnything.actions.communityDesc": "Відкрита Discord-спільнота",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Створіть створений штучним інтелектом робочий процес або процес чату",
"gotoAnything.actions.createChatflow": "Потік чату",
"gotoAnything.actions.createChatflowDesc": "Створіть програму chatflow (розширений чат) з опису",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Після відновлення версії нинішній проект буде перезаписано.",
"versionHistory.title": "Версії",
"workflowGenerator.applied": "Прикладний",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Не вдалося застосувати робочий процес",
"workflowGenerator.applyToCurrent": "Застосувати до поточної чернетки",
"workflowGenerator.applyToNew": "Створити новий додаток",
"workflowGenerator.cancel": "Скасувати",
"workflowGenerator.description": "Опишіть, що ви хочете робити в робочому процесі. Виберіть модель, напишіть інструкцію та попередньо перегляньте згенерований графік перед застосуванням його в Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Відхилити",
"workflowGenerator.errors.DANGLING_EDGE": "Згенерований робочий процес має край, який вказує на вузол, якого не існує. Спробуйте ще раз або уточніть свою інструкцію.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Згенерований робочий процес містить повторювані ID вузлів. Спробуйте згенерувати знову.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "обочий цикл посилається на інструмент, який не встановлено для цієї робочої області. Встановіть його зі сторінки Інструменти або вдосконаліть його інструкцію.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Вузол у згенерованому робочому процесі посилається на змінну, яка не оголошена вище. Спробуйте регенерувати.",
"workflowGenerator.errors.apply_failed_orphan": "Не вдалося завершити створення програми. Можливо, у вашому списку програм залишилася порожня чернетка — видаліть її вручну.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Чернетку робочого циклу було відредаговано на іншій вкладці. Перезавантажте, щоб отримати ці зміни, а потім спробуйте застосувати ще раз.",
"workflowGenerator.errors.hash_collision_title": "обочу область була відредаговано в іншому місці",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Генерація тривала занадто довго. Можливо, модель працює повільно або недоступна — повторіть спробу.",
"workflowGenerator.examples.chatflow.support": "Бот підтримки клієнтів, що підтримується базою знань",
"workflowGenerator.examples.chatflow.triage": "Сортуйте вхідні запитання та направляйте їх до спеціаліста",
"workflowGenerator.examples.chatflow.tutor": "Багатомовний репетитор, який пояснює крок за кроком",
"workflowGenerator.examples.label": "Спробуйте один із них",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Отримайте проблеми GitHub і класифікуйте їх",
"workflowGenerator.examples.workflow.rag": "Запит до бази знань, а потім відформатуйте відповідь як Markdown",
"workflowGenerator.examples.workflow.summarize": "Узагальніть URL-адресу",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Не вдалося завантажити поточну чернетку — генерація почнеться з нуля.",
"workflowGenerator.refineInstructionPlaceholder": "Опишіть зміну — напр. додати крок перекладу, перейти на інструмент, додати обробку помилок.",
"workflowGenerator.refineTitle": "Уточнити {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Перезавантажити",
"workflowGenerator.studioApply": "Застосувати",
"workflowGenerator.studioButton": "Генерувати",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "Chưa có ứng dụng",
"gotoAnything.actions.accountDesc": "Đi đến trang tài khoản",
"gotoAnything.actions.communityDesc": "Mở cộng đồng Discord",
"gotoAnything.actions.createAuto": "Auto",
"gotoAnything.actions.createAutoDesc": "Let AI pick Workflow or Chatflow from your description",
"gotoAnything.actions.createCategoryDesc": "Tạo quy trình làm việc hoặc luồng trò chuyện do AI tạo",
"gotoAnything.actions.createChatflow": "Luồng trò chuyện",
"gotoAnything.actions.createChatflowDesc": "Tạo ứng dụng luồng trò chuyện (trò chuyện nâng cao) từ mô tả",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "Sau khi phục hồi phiên bản, bản nháp hiện tại sẽ bị ghi đè.",
"versionHistory.title": "Các phiên bản",
"workflowGenerator.applied": "Đã áp dụng",
"workflowGenerator.appliedRefineHint": "Applied — refine anytime with ⌘K → /refine",
"workflowGenerator.applyFailed": "Không thể áp dụng quy trình làm việc",
"workflowGenerator.applyToCurrent": "Áp dụng cho dự thảo hiện tại",
"workflowGenerator.applyToNew": "Tạo ứng dụng mới",
"workflowGenerator.cancel": "Hủy bỏ",
"workflowGenerator.description": "Mô tả quy trình làm việc mà bạn muốn. Chọn một mô hình, viết hướng dẫn, và xem trước biểu đồ đã tạo trước khi áp dụng vào Studio.",
"workflowGenerator.diff.summary": "Added {{added}} · Removed {{removed}} · Changed {{changed}}",
"workflowGenerator.dismiss": "Bỏ qua",
"workflowGenerator.errors.DANGLING_EDGE": "Quy trình làm việc được tạo có một cạnh trỏ đến nút không tồn tại. Hãy thử lại hoặc tinh chỉnh hướng dẫn của bạn.",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "Quy trình được tạo chứa các ID nút trùng lặp. Hãy thử tạo lại.",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "Quy trình làm việc tham chiếu một công cụ chưa được cài đặt cho không gian làm việc này. Hãy cài đặt từ trang Công cụ hoặc tinh chỉnh hướng dẫn của bạn.",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "Một nút trong quy trình làm việc được tạo sẽ tham chiếu một biến không được khai báo ở thượng nguồn. Hãy thử tạo lại.",
"workflowGenerator.errors.apply_failed_orphan": "Không thể hoàn tất việc tạo ứng dụng. Một bản nháp trống có thể đã bị bỏ lại trong danh sách ứng dụng của bạn — vui lòng xóa thủ công.",
"workflowGenerator.errors.atNode": "Affected node: {{node}}",
"workflowGenerator.errors.hash_collision": "Bản nháp quy trình làm việc đã được chỉnh sửa ở tab khác. Hãy tải lại để nhận các thay đổi đó, rồi thử áp dụng lại.",
"workflowGenerator.errors.hash_collision_title": "Không gian làm việc đã được chỉnh sửa ở nơi khác",
"workflowGenerator.errors.installTools": "Install tools",
"workflowGenerator.errors.timeout": "Việc tạo mất quá nhiều thời gian. Mô hình có thể chậm hoặc không khả dụng — hãy thử lại.",
"workflowGenerator.examples.chatflow.support": "Bot hỗ trợ khách hàng được hỗ trợ bởi cơ sở kiến thức",
"workflowGenerator.examples.chatflow.triage": "Phân loại các câu hỏi đến và chuyển đến lời nhắc của chuyên gia",
"workflowGenerator.examples.chatflow.tutor": "Gia sư đa ngôn ngữ giải thích từng bước",
"workflowGenerator.examples.label": "Hãy thử một trong những lựa chọn này",
"workflowGenerator.examples.refresh": "More ideas",
"workflowGenerator.examples.workflow.classify": "Tìm nạp các vấn đề GitHub và phân loại chúng",
"workflowGenerator.examples.workflow.rag": "Truy vấn cơ sở kiến thức, sau đó định dạng câu trả lời dưới dạng Markdown",
"workflowGenerator.examples.workflow.summarize": "Tóm tắt một URL",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "Không thể tải bản nháp hiện tại — sẽ tạo từ đầu.",
"workflowGenerator.refineInstructionPlaceholder": "Mô tả sự thay đổi - ví dụ: thêm bước dịch, chuyển sang công cụ, thêm xử lý lỗi.",
"workflowGenerator.refineTitle": "Tinh chỉnh {{mode}}",
"workflowGenerator.regenerate": "Regenerate",
"workflowGenerator.reload": "Tải lại",
"workflowGenerator.studioApply": "Áp dụng",
"workflowGenerator.studioButton": "Tạo",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "创建你的第一个应用",
"gotoAnything.actions.accountDesc": "导航到账户页面",
"gotoAnything.actions.communityDesc": "打开 Discord 社区",
"gotoAnything.actions.createAuto": "自动",
"gotoAnything.actions.createAutoDesc": "让 AI 根据你的描述自动选择 Workflow 或 Chatflow",
"gotoAnything.actions.createCategoryDesc": "创建由 AI 生成的工作流或 Chatflow",
"gotoAnything.actions.createChatflow": "Chatflow",
"gotoAnything.actions.createChatflowDesc": "根据描述生成一个 Chatflow(高级聊天)应用",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "版本回滚后,当前草稿将被覆盖。",
"versionHistory.title": "版本",
"workflowGenerator.applied": "已应用",
"workflowGenerator.appliedRefineHint": "已应用 — 随时用 ⌘K → /refine 优化",
"workflowGenerator.applyFailed": "应用工作流失败",
"workflowGenerator.applyToCurrent": "应用到当前草稿",
"workflowGenerator.applyToNew": "创建新应用",
"workflowGenerator.cancel": "取消",
"workflowGenerator.description": "描述你希望工作流完成的任务。选择模型、撰写指令,预览生成的图后再应用到 Studio。",
"workflowGenerator.diff.summary": "新增 {{added}} · 删除 {{removed}} · 修改 {{changed}}",
"workflowGenerator.dismiss": "关闭",
"workflowGenerator.errors.DANGLING_EDGE": "生成的工作流中有连线指向了不存在的节点,请重试或细化指令。",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "生成的工作流中存在重复的节点 ID,请重新生成。",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "工作流引用了当前工作空间未安装的工具。请先在「工具」页面安装该工具,或细化指令。",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "工作流中有节点引用了上游未声明的变量,请重新生成。",
"workflowGenerator.errors.apply_failed_orphan": "未能完成应用,应用列表中可能残留一个空草稿——请手动删除。",
"workflowGenerator.errors.atNode": "受影响的节点:{{node}}",
"workflowGenerator.errors.hash_collision": "工作流草稿被另一个标签页修改过,请重新加载以同步那些改动后再尝试应用。",
"workflowGenerator.errors.hash_collision_title": "工作空间在别处被修改",
"workflowGenerator.errors.installTools": "安装工具",
"workflowGenerator.errors.timeout": "生成耗时过长。模型可能较慢或不可用——请重试。",
"workflowGenerator.examples.chatflow.support": "基于知识库的客服机器人",
"workflowGenerator.examples.chatflow.triage": "分诊问题并路由到对应的专业 Prompt",
"workflowGenerator.examples.chatflow.tutor": "多语言导师,分步骤讲解",
"workflowGenerator.examples.label": "试试这些",
"workflowGenerator.examples.refresh": "换一批",
"workflowGenerator.examples.workflow.classify": "拉取 GitHub Issue 并分类",
"workflowGenerator.examples.workflow.rag": "查询知识库,然后以 Markdown 格式输出答案",
"workflowGenerator.examples.workflow.summarize": "总结一个网址",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "无法加载当前草稿,将改为从头生成。",
"workflowGenerator.refineInstructionPlaceholder": "描述修改内容——例如添加翻译步骤、改用某个工具、增加错误处理。",
"workflowGenerator.refineTitle": "优化 {{mode}}",
"workflowGenerator.regenerate": "重新生成",
"workflowGenerator.reload": "重新加载",
"workflowGenerator.studioApply": "应用",
"workflowGenerator.studioButton": "生成",
+2
View File
@@ -55,6 +55,8 @@
"firstEmpty.title": "尚無應用程式",
"gotoAnything.actions.accountDesc": "導航到帳戶頁面",
"gotoAnything.actions.communityDesc": "開放的 Discord 社區",
"gotoAnything.actions.createAuto": "自動",
"gotoAnything.actions.createAutoDesc": "讓 AI 根據你的描述自動選擇 Workflow 或 Chatflow",
"gotoAnything.actions.createCategoryDesc": "建立 AI 產生的工作流程或聊天流程",
"gotoAnything.actions.createChatflow": "聊天串流",
"gotoAnything.actions.createChatflowDesc": "根据描述生成聊天流(高级聊天)应用程序",
+6
View File
@@ -1305,11 +1305,13 @@
"versionHistory.restorationTip": "版本恢復後,當前草稿將被覆蓋。",
"versionHistory.title": "版本",
"workflowGenerator.applied": "應用",
"workflowGenerator.appliedRefineHint": "已套用 — 隨時用 ⌘K → /refine 優化",
"workflowGenerator.applyFailed": "應用工作流程失敗",
"workflowGenerator.applyToCurrent": "適用於當前草案",
"workflowGenerator.applyToNew": "創建新應用程式",
"workflowGenerator.cancel": "取消",
"workflowGenerator.description": "描述您希望工作流程執行的操作。選擇模型、編寫指令並然後再將其套用到 工作室覽生成的圖形,单间公寓。",
"workflowGenerator.diff.summary": "新增 {{added}} · 刪除 {{removed}} · 變更 {{changed}}",
"workflowGenerator.dismiss": "解僱",
"workflowGenerator.errors.DANGLING_EDGE": "產生的工作流程有一條邊指向不存在的節點。再試一次或完善您的指示。",
"workflowGenerator.errors.DUPLICATE_NODE_ID": "生成的工作流中存在重複的節點 ID,請重新生成。",
@@ -1327,13 +1329,16 @@
"workflowGenerator.errors.UNKNOWN_TOOL": "工作流引用了未为此工作区安装的工具。从“工具”页面安装或完善您的说明。",
"workflowGenerator.errors.UNRESOLVED_REFERENCE": "產生的工作流程中的節點引用未在上游宣告的變數。嘗試再生。",
"workflowGenerator.errors.apply_failed_orphan": "我們無法完成應用程式的創建。您的應用程式清單中可能留下了空草稿 - 請手動將其刪除。",
"workflowGenerator.errors.atNode": "受影響的節點:{{node}}",
"workflowGenerator.errors.hash_collision": "工作流草稿已在另一个选项卡中编辑。重新加载以获取这些更改,然后尝试重新应用。",
"workflowGenerator.errors.hash_collision_title": "工作區已在其他地方編輯",
"workflowGenerator.errors.installTools": "安裝工具",
"workflowGenerator.errors.timeout": "一代人花了太長時間。該模型可能很慢或不可用 - 請重試。",
"workflowGenerator.examples.chatflow.support": "由知識庫支援的客戶支援機器人",
"workflowGenerator.examples.chatflow.triage": "將收到的問題分類並轉至專家提示",
"workflowGenerator.examples.chatflow.tutor": "多語言導師一步步講解",
"workflowGenerator.examples.label": "嘗試其中之一",
"workflowGenerator.examples.refresh": "換一批",
"workflowGenerator.examples.workflow.classify": "取得 GitHub 問題並對其進行分類",
"workflowGenerator.examples.workflow.rag": "中知識庫查詢文(式化為简体然後將答案) ,中文(繁体)",
"workflowGenerator.examples.workflow.summarize": "總結一個網址",
@@ -1357,6 +1362,7 @@
"workflowGenerator.refineDraftUnavailable": "無法載入目前草稿,將改為從頭生成。",
"workflowGenerator.refineInstructionPlaceholder": "描述變更-例如新增翻譯步驟、切換到工具、新增錯誤處理。",
"workflowGenerator.refineTitle": "精煉{{mode}}",
"workflowGenerator.regenerate": "重新生成",
"workflowGenerator.reload": "重新載入",
"workflowGenerator.studioApply": "申請",
"workflowGenerator.studioButton": "產生",
@@ -0,0 +1,100 @@
// Testing the SSE helper requires importing the module under test directly.
// eslint-disable-next-line no-restricted-imports
import { sseGeneratorPost } from '../base'
vi.mock('@/utils/var', () => ({
basePath: '/app',
API_PREFIX: '/console/api',
PUBLIC_API_PREFIX: '/api',
IS_CE_EDITION: false,
}))
// Minimal streaming Response: a body whose reader yields the given chunks in
// order, then signals done. ``json()`` backs the non-2xx error path.
const makeStreamResponse = (chunks: string[], status = 200) => {
const encoder = new TextEncoder()
let i = 0
return {
status,
body: {
getReader: () => ({
read: () =>
i < chunks.length
? Promise.resolve({ done: false, value: encoder.encode(chunks[i++]) })
: Promise.resolve({ done: true, value: undefined }),
}),
},
json: () => Promise.resolve({ message: 'Server Error' }),
} as unknown as Response
}
describe('sseGeneratorPost', () => {
afterEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
it('streams plan then result events, hands back an AbortController, and completes', async () => {
const planFrame = `data: ${JSON.stringify({ event: 'plan', title: 'X', nodes: [] })}\n\n`
const resultFrame = `data: ${JSON.stringify({ event: 'result', graph: { nodes: [] } })}\n\n`
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(makeStreamResponse([planFrame, resultFrame])))
const onPlan = vi.fn()
const onResult = vi.fn()
const onCompleted = vi.fn()
const onError = vi.fn()
let controller: AbortController | undefined
sseGeneratorPost('/workflow-generate/stream', { mode: 'workflow' }, {
onPlan,
onResult,
onCompleted,
onError,
getAbortController: (c) => { controller = c },
})
await vi.waitFor(() => expect(onCompleted).toHaveBeenCalledTimes(1))
expect(controller).toBeInstanceOf(AbortController)
expect(onPlan).toHaveBeenCalledWith(expect.objectContaining({ event: 'plan', title: 'X' }))
expect(onResult).toHaveBeenCalledWith(expect.objectContaining({ event: 'result' }))
expect(onError).not.toHaveBeenCalled()
// POSTs to the prefixed stream URL.
const [calledUrl, calledOpts] = (globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0]!
expect(String(calledUrl)).toContain('/workflow-generate/stream')
expect((calledOpts as RequestInit).method).toBe('POST')
})
it('reassembles a JSON frame split across chunk boundaries', async () => {
const frame = JSON.stringify({ event: 'result', graph: { nodes: [] } })
const part1 = `data: ${frame.slice(0, 10)}`
const part2 = `${frame.slice(10)}\n\n`
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(makeStreamResponse([part1, part2])))
const onResult = vi.fn()
const onCompleted = vi.fn()
sseGeneratorPost('/workflow-generate/stream', {}, { onResult, onCompleted })
await vi.waitFor(() => expect(onCompleted).toHaveBeenCalled())
expect(onResult).toHaveBeenCalledTimes(1)
})
it('reports a non-2xx (non-401) response through onError', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(makeStreamResponse([], 500)))
const onError = vi.fn()
sseGeneratorPost('/workflow-generate/stream', {}, { onError })
await vi.waitFor(() => expect(onError).toHaveBeenCalledWith('Server Error'))
})
it('reports a rejected fetch through onError', async () => {
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network down')))
const onError = vi.fn()
sseGeneratorPost('/workflow-generate/stream', {}, { onError })
await vi.waitFor(() => expect(onError).toHaveBeenCalled())
expect(onError.mock.calls[0]![0]).toContain('network down')
})
})
+12 -1
View File
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { handleStream } from './base'
// eslint-disable-next-line no-restricted-imports
import { del, get, handleStream, patch, post, put } from './base'
describe('handleStream', () => {
beforeEach(() => {
@@ -280,3 +281,13 @@ describe('handleStream', () => {
})
})
})
describe('HTTP methods', () => {
it('should export methods correctly', () => {
expect(typeof get).toBe('function')
expect(typeof post).toBe('function')
expect(typeof put).toBe('function')
expect(typeof patch).toBe('function')
expect(typeof del).toBe('function')
})
})
+97
View File
@@ -767,6 +767,103 @@ export const sseGet = async (
})
}
export type GeneratorStreamCallbacks = {
/** Fired once when the planner stage finishes — carries the high-level plan. */
onPlan?: (data: Record<string, unknown>) => void
/** Fired once when the builder + validation finish — carries the final graph envelope. */
onResult?: (data: Record<string, unknown>) => void
onError?: (message: string) => void
onCompleted?: () => void
getAbortController?: (abortController: AbortController) => void
}
/**
* Dedicated SSE consumer for the workflow generator's plan-first stream
* (`/workflow-generate/stream`). Kept separate from ``ssePost`` /
* ``handleStream`` on purpose: those are wired to the chat / workflow-run event
* vocabulary (``message``, ``node_finished``, …) and threading two more
* positional callbacks through that shared, high-blast-radius path isn't worth
* it. This helper reuses the same cookie-auth + CSRF + abort setup but
* only understands the generator's two events: ``plan`` then ``result``.
*/
export const sseGeneratorPost = (
url: string,
body: unknown,
{ onPlan, onResult, onError, onCompleted, getAbortController }: GeneratorStreamCallbacks,
) => {
const abortController = new AbortController()
const baseOptions = getBaseOptions()
const options = Object.assign({}, baseOptions, {
method: 'POST',
signal: abortController.signal,
headers: new Headers({
[CSRF_HEADER_NAME]: Cookies.get(CSRF_COOKIE_NAME())! || '',
'Content-Type': ContentType.json,
}),
body: JSON.stringify(body),
} as RequestInit)
getAbortController?.(abortController)
const urlWithPrefix = formatURL(url, false)
const fail = (e: unknown) => {
// Aborts are intentional (modal close / regenerate) — never surface them.
if (e instanceof Error && e.name === 'AbortError')
return
onError?.(`${e}`)
}
globalThis.fetch(urlWithPrefix, options as RequestInit)
.then((res) => {
if (!/^[23]\d{2}$/.test(String(res.status))) {
if (res.status === 401) {
refreshAccessTokenOrReLogin(TIME_OUT)
.then(() => sseGeneratorPost(url, body, { onPlan, onResult, onError, onCompleted, getAbortController }))
.catch(() => onError?.('Unauthorized'))
return
}
res.json().then((data: { message?: string }) => onError?.(data?.message || 'Server Error')).catch(() => onError?.('Server Error'))
return
}
const reader = res.body?.getReader()
const decoder = new TextDecoder('utf-8')
let buffer = ''
const read = () => {
reader?.read().then(({ done, value }) => {
if (done) {
onCompleted?.()
return
}
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
// Process every complete line; keep the trailing partial in the buffer.
lines.slice(0, -1).forEach((message) => {
if (!message.startsWith('data: '))
return
let obj: Record<string, unknown>
try {
obj = JSON.parse(message.slice(6))
}
catch {
// A chunk boundary split the JSON — it'll re-arrive intact next read.
return
}
if (obj.event === 'plan')
onPlan?.(obj)
else if (obj.event === 'result')
onResult?.(obj)
})
buffer = lines[lines.length - 1] || ''
read()
}).catch(fail)
}
read()
})
.catch(fail)
}
// base request
export const request = async<T>(url: string, options = {}, otherOptions?: IOtherOptions) => {
try {
+98 -2
View File
@@ -1,9 +1,22 @@
import type { AppModeEnum } from '@/types/app'
// service/base is the dependency we're mocking in this test; the
// no-restricted-imports rule targets production imports, not test
// instrumentation — mirrors sibling service specs (annotation.spec.ts etc.).
// eslint-disable-next-line no-restricted-imports
import { post } from './base'
import { generateWorkflow } from './debug'
import { get, post, sseGeneratorPost, ssePost } from './base'
import {
fetchConversationMessages,
fetchPromptTemplate,
fetchSuggestedQuestions,
fetchTextGenerationMessage,
fetchWorkflowInstructionSuggestions,
generateBasicAppFirstTimeRule,
generateRule,
generateWorkflow,
generateWorkflowStream,
sendCompletionMessage,
stopChatMessageResponding,
} from './debug'
// Stub the shared `post` wrapper so tests verify only what `generateWorkflow`
// composes on top of it — URL, body, and the typed response surface.
@@ -11,6 +24,7 @@ vi.mock('./base', () => ({
post: vi.fn(),
get: vi.fn(),
ssePost: vi.fn(),
sseGeneratorPost: vi.fn(),
}))
describe('debug service — generateWorkflow', () => {
@@ -80,4 +94,86 @@ describe('debug service — generateWorkflow', () => {
expect(post).toHaveBeenCalledWith('/workflow-generate', { body })
expect(vi.mocked(post).mock.calls[0]).toHaveLength(2)
})
describe('other endpoints', () => {
it('stopChatMessageResponding', async () => {
await stopChatMessageResponding('app-1', 'task-1')
expect(post).toHaveBeenCalledWith('apps/app-1/chat-messages/task-1/stop')
})
it('sendCompletionMessage', async () => {
const callbacks = { onData: vi.fn(), onCompleted: vi.fn(), onError: vi.fn(), onMessageReplace: vi.fn() }
await sendCompletionMessage('app-1', { text: 'hello' }, callbacks)
expect(ssePost).toHaveBeenCalledWith('apps/app-1/completion-messages', {
body: { text: 'hello', response_mode: 'streaming' },
}, callbacks)
})
it('fetchSuggestedQuestions', async () => {
const getAbortController = vi.fn()
await fetchSuggestedQuestions('app-1', 'msg-1', getAbortController)
expect(get).toHaveBeenCalledWith('apps/app-1/chat-messages/msg-1/suggested-questions', {}, { getAbortController })
})
it('fetchConversationMessages', async () => {
const getAbortController = vi.fn()
await fetchConversationMessages('app-1', 'conv-1', getAbortController)
expect(get).toHaveBeenCalledWith('apps/app-1/chat-messages', { params: { conversation_id: 'conv-1' } }, { getAbortController })
})
it('generateBasicAppFirstTimeRule', async () => {
await generateBasicAppFirstTimeRule({ mode: 'chat' })
expect(post).toHaveBeenCalledWith('/rule-generate', { body: { mode: 'chat' } })
})
it('generateRule', async () => {
await generateRule({ mode: 'chat' })
expect(post).toHaveBeenCalledWith('/instruction-generate', { body: { mode: 'chat' } })
})
it('generateWorkflowStream', async () => {
const body = { mode: 'workflow' as const, instruction: 'test', model_config: { provider: 'test', name: 'test', mode: 'chat' } }
const callbacks = { onPlan: vi.fn(), onResult: vi.fn(), onError: vi.fn(), onCompleted: vi.fn(), getAbortController: vi.fn() }
vi.mocked(sseGeneratorPost).mockImplementation((_url, _body, options) => {
options?.onPlan?.({ title: 'plan' })
options?.onResult?.({ graph: { nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 } } })
return Promise.resolve()
})
await generateWorkflowStream(body, callbacks)
expect(sseGeneratorPost).toHaveBeenCalled()
expect(callbacks.onPlan).toHaveBeenCalled()
expect(callbacks.onResult).toHaveBeenCalled()
})
it('fetchWorkflowInstructionSuggestions without getAbortController', async () => {
await fetchWorkflowInstructionSuggestions({ mode: 'workflow' })
expect(post).toHaveBeenCalledWith('/workflow-generate/suggestions', { body: { mode: 'workflow' } })
})
it('fetchWorkflowInstructionSuggestions with getAbortController', async () => {
const getAbortController = vi.fn()
await fetchWorkflowInstructionSuggestions({ mode: 'workflow' }, { getAbortController })
expect(post).toHaveBeenCalledWith('/workflow-generate/suggestions', { body: { mode: 'workflow' } }, { getAbortController })
})
it('fetchPromptTemplate', async () => {
await fetchPromptTemplate({ appMode: 'chat' as AppModeEnum, mode: 'chat', modelName: 'gpt-4', hasSetDataSet: true })
expect(get).toHaveBeenCalledWith('/app/prompt-templates', {
params: {
app_mode: 'chat',
model_mode: 'chat',
model_name: 'gpt-4',
has_context: true,
},
})
})
it('fetchTextGenerationMessage', async () => {
await fetchTextGenerationMessage({ appId: 'app-1', messageId: 'msg-1' })
expect(get).toHaveBeenCalledWith('/apps/app-1/messages/msg-1')
})
})
})
+96 -2
View File
@@ -3,7 +3,7 @@ import type { IOnCompleted, IOnData, IOnError, IOnMessageReplace } from './base'
import type { Edge, Node } from '@/app/components/workflow/types'
import type { ChatPromptConfig, CompletionPromptConfig } from '@/models/debug'
import type { AppModeEnum, ModelModeType } from '@/types/app'
import { get, post, ssePost } from './base'
import { get, post, sseGeneratorPost, ssePost } from './base'
type BasicAppFirstRes = {
prompt: string
@@ -122,6 +122,12 @@ export type GenerateWorkflowResponse = {
* the planner omits it; the caller supplies a 🤖 fallback.
*/
icon?: string
/**
* Resolved app mode. Echoes the request mode, except when the request used
* ``mode: 'auto'`` — then this is the concrete mode the planner picked, which
* the caller uses to decide which app type to create.
*/
mode?: 'workflow' | 'advanced-chat'
/** Human-readable concatenation of ``errors[].detail``. "" on success. */
error?: string
/** Structured errors with stable codes for FE-localised mapping. [] on success. */
@@ -129,7 +135,8 @@ export type GenerateWorkflowResponse = {
}
export type GenerateWorkflowBody = {
mode: 'workflow' | 'advanced-chat'
/** ``'auto'`` lets the planner pick Workflow vs Chatflow; the resolved mode comes back on the response. */
mode: 'workflow' | 'advanced-chat' | 'auto'
instruction: string
ideal_output?: string
model_config: { provider: string, name: string, mode: string, completion_params?: Record<string, unknown> }
@@ -167,6 +174,93 @@ export const generateWorkflow = (body: GenerateWorkflowBody, options?: GenerateW
return post<GenerateWorkflowResponse>('/workflow-generate', { body })
}
// ─── Plan-first streaming (cmd+k generator) ──────────────────────────────────
/** One node in the planner's high-level plan, shown before the graph builds. */
type WorkflowGenPlanNode = {
label: string
node_type: string
purpose?: string
}
/** A start-node input the generated app will ask the end-user for. */
type WorkflowGenPlanInput = {
variable: string
label?: string
type?: string
}
/** The planner result, streamed ahead of the built graph as the ``plan`` event. */
export type WorkflowGenPlan = {
title?: string
description?: string
app_name?: string
icon?: string
/** Resolved mode — concrete even when the request used ``mode: 'auto'``. */
mode?: 'workflow' | 'advanced-chat'
nodes: WorkflowGenPlanNode[]
start_inputs?: WorkflowGenPlanInput[]
}
export type GenerateWorkflowStreamCallbacks = {
onPlan?: (plan: WorkflowGenPlan) => void
onResult?: (result: GenerateWorkflowResponse) => void
onError?: (message: string) => void
onCompleted?: () => void
getAbortController?: (controller: AbortController) => void
}
/**
* Plan-first streaming variant of ``generateWorkflow``. The backend emits the
* planner result (``onPlan``) as soon as it's ready — typically a few seconds
* in — then the built graph (``onResult``) once the builder + validation
* finish. Lets the modal show real progress (an outline of the plan) instead
* of a guessed phase timer, and surfaces the graph the moment it lands.
*/
export const generateWorkflowStream = (
body: GenerateWorkflowBody,
callbacks: GenerateWorkflowStreamCallbacks,
) => {
return sseGeneratorPost('/workflow-generate/stream', body, {
onPlan: data => callbacks.onPlan?.(data as unknown as WorkflowGenPlan),
onResult: data => callbacks.onResult?.(data as unknown as GenerateWorkflowResponse),
onError: callbacks.onError,
onCompleted: callbacks.onCompleted,
getAbortController: callbacks.getAbortController,
})
}
// ─── AI-generated instruction suggestions (generator "ideas" chips) ───────────
export type WorkflowInstructionSuggestionsBody = {
mode: 'workflow' | 'advanced-chat'
/** UI language so suggestions come back localized, e.g. 'zh-Hans'. */
language?: string
count?: number
}
export type WorkflowInstructionSuggestionsResponse = {
suggestions: string[]
}
/**
* Fetch a handful of short, workspace-grounded example instructions for the
* generator's "ideas" chips. Backed by the tenant default model; the backend
* soft-fails to ``{ suggestions: [] }`` (no toast), so the caller falls back to
* its static curated list on an empty result.
*/
export const fetchWorkflowInstructionSuggestions = (
body: WorkflowInstructionSuggestionsBody,
options?: GenerateWorkflowOptions,
) => {
if (options?.getAbortController) {
return post<WorkflowInstructionSuggestionsResponse>('/workflow-generate/suggestions', { body }, {
getAbortController: options.getAbortController,
})
}
return post<WorkflowInstructionSuggestionsResponse>('/workflow-generate/suggestions', { body })
}
export const fetchPromptTemplate = ({
appMode,
mode,