fix(flows): show plain-language step labels in the workflow proposal card (#5088)

This commit is contained in:
Cyrus Gray
2026-07-21 23:48:03 +05:30
committed by GitHub
parent 4b2d92c033
commit c309be81fe
16 changed files with 290 additions and 18 deletions
@@ -56,17 +56,89 @@ describe('WorkflowProposalCard', () => {
mockNavigate.mockReset();
});
it('renders the name, trigger, and steps with node-kind badges', () => {
it('renders the name, trigger, and steps with plain-language node-kind badges', () => {
render(<WorkflowProposalCard threadId="t1" proposal={proposal()} />);
expect(screen.getByText('Daily standup summary')).toBeInTheDocument();
expect(screen.getByText('schedule: 0 9 * * *')).toBeInTheDocument();
expect(screen.getByText('Summarize')).toBeInTheDocument();
expect(screen.getByText('Post to Slack')).toBeInTheDocument();
expect(screen.getByText('agent')).toBeInTheDocument();
expect(screen.getByText('tool_call')).toBeInTheDocument();
// Badges show the friendly i18n key (useT is mocked to echo the key),
// never the raw snake_case wire `kind`.
expect(screen.getByText('chat.flowProposal.stepKind.agent')).toBeInTheDocument();
expect(screen.getByText('chat.flowProposal.stepKind.toolCall')).toBeInTheDocument();
expect(screen.queryByText('agent')).not.toBeInTheDocument();
expect(screen.queryByText('tool_call')).not.toBeInTheDocument();
expect(screen.getAllByTestId('workflow-proposal-step-kind')).toHaveLength(2);
});
it('hides config_hint from the card even when present on the step', () => {
render(<WorkflowProposalCard threadId="t1" proposal={proposal()} />);
// `proposal()` sets a config_hint on the first step; the card must not
// surface it (it can leak internal identifiers like tool slugs or URLs).
expect(screen.queryByText("Summarize yesterday's messages")).not.toBeInTheDocument();
});
it('falls back to a humanized label for an unrecognized step kind', () => {
render(
<WorkflowProposalCard
threadId="t1"
proposal={proposal({
summary: {
trigger: 'manual',
steps: [{ kind: 'future_thing', name: 'Do the new thing' }],
},
})}
/>
);
// Not in STEP_KIND_I18N_KEYS, so it must fall back to the pure
// capitalize + underscores-to-spaces helper rather than the raw kind.
expect(screen.getByText('Future thing')).toBeInTheDocument();
expect(screen.queryByText('future_thing')).not.toBeInTheDocument();
});
// `step.kind` is arbitrary wire data. A plain bracket index would resolve
// inherited Object members (e.g. `constructor` -> the Object function) and
// hand a non-string to t(), breaking the badge render. The own-property
// guard must send these through the humanized fallback instead. `constructor`
// and `toString` have no underscores, so the humanized label is just the
// capitalized kind.
it.each(['constructor', 'toString'])(
'humanizes the inherited-property kind %s instead of resolving it on the prototype',
kind => {
render(
<WorkflowProposalCard
threadId="t1"
proposal={proposal({
summary: { trigger: 'manual', steps: [{ kind, name: 'Edge-case step' }] },
})}
/>
);
const expected = kind.charAt(0).toUpperCase() + kind.slice(1);
expect(screen.getByText(expected)).toBeInTheDocument();
expect(screen.getByText('Edge-case step')).toBeInTheDocument();
}
);
it('renders a __proto__ step kind without leaking an inherited property', () => {
render(
<WorkflowProposalCard
threadId="t1"
proposal={proposal({
summary: { trigger: 'manual', steps: [{ kind: '__proto__', name: 'Proto step' }] },
})}
/>
);
// The own-property guard treats __proto__ as unknown and humanizes it, so
// the row still renders (step name present) and the badge shows a real
// string. Assert the badge's exact (whitespace-normalized) content directly
// — not just the step name / absence of "function" — so a missing badge or
// an inherited-member leak like `[object Object]` would fail here too.
// `__proto__` humanizes (underscores -> spaces, first char already a space)
// to a lowercase "proto" badge.
expect(screen.getByText('Proto step')).toBeInTheDocument();
expect(screen.getByTestId('workflow-proposal-step-kind')).toHaveTextContent(/^proto$/);
});
it('has the expected root test id', () => {
render(<WorkflowProposalCard threadId="t1" proposal={proposal()} />);
expect(screen.getByTestId('workflow-proposal-card')).toBeInTheDocument();
@@ -16,6 +16,50 @@ import Button from '../ui/Button';
const log = debug('openhuman:chat:workflow-proposal-card');
// Maps the wire `step.kind` (a `tinyflows` node type, snake_case, e.g.
// `tool_call`) to the i18n key for its plain-language badge label. Kinds not
// in this map (e.g. a future node type the frontend doesn't know about yet)
// fall back to `humanizeUnknownStepKind` below rather than showing the raw
// snake_case identifier to a non-technical user.
const STEP_KIND_I18N_KEYS: Record<string, string> = {
agent: 'chat.flowProposal.stepKind.agent',
tool_call: 'chat.flowProposal.stepKind.toolCall',
http_request: 'chat.flowProposal.stepKind.httpRequest',
code: 'chat.flowProposal.stepKind.code',
condition: 'chat.flowProposal.stepKind.condition',
switch: 'chat.flowProposal.stepKind.switch',
merge: 'chat.flowProposal.stepKind.merge',
split_out: 'chat.flowProposal.stepKind.splitOut',
transform: 'chat.flowProposal.stepKind.transform',
output_parser: 'chat.flowProposal.stepKind.outputParser',
sub_workflow: 'chat.flowProposal.stepKind.subWorkflow',
};
/**
* Pure fallback for a `step.kind` the frontend doesn't recognize: capitalize
* the first letter and turn `_` into spaces (e.g. `future_thing` ->
* `Future thing`) so an unmapped kind still reads as plain language instead
* of a raw snake_case identifier.
*/
function humanizeUnknownStepKind(kind: string): string {
const spaced = kind.replace(/_/g, ' ');
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
}
/**
* Resolve the i18n key for a mapped `step.kind`, guarding against inherited
* Object properties. `step.kind` is arbitrary wire data, so a plain bracket
* index (`STEP_KIND_I18N_KEYS[step.kind]`) would resolve inherited members for
* values like `constructor` or `__proto__` — handing a function/object to
* `t()` and breaking the badge render. Only own keys count; anything else
* returns `undefined` so the caller falls back to `humanizeUnknownStepKind`.
*/
function stepKindI18nKey(kind: string): string | undefined {
return Object.prototype.hasOwnProperty.call(STEP_KIND_I18N_KEYS, kind)
? STEP_KIND_I18N_KEYS[kind]
: undefined;
}
interface Props {
threadId: string;
proposal: WorkflowProposal;
@@ -183,23 +227,22 @@ export const WorkflowProposalCard: React.FC<Props> = ({ threadId, proposal, onSa
</p>
{proposal.summary.steps.length > 0 ? (
<ol className="mt-1 max-h-56 list-decimal overflow-y-auto pl-6 text-content-secondary">
{proposal.summary.steps.map((step, i) => (
<li key={i} className="break-words">
<span
data-testid="workflow-proposal-step-kind"
className="mr-1.5 inline-block rounded-full bg-ocean-100 px-1.5 py-0.5 text-[10px] font-medium text-ocean-700 dark:bg-ocean-500/15 dark:text-ocean-300">
{step.kind}
</span>
<span>{step.name}</span>
{step.config_hint ? (
{proposal.summary.steps.map((step, i) => {
const kindI18nKey = stepKindI18nKey(step.kind);
const kindLabel = kindI18nKey
? t(kindI18nKey)
: humanizeUnknownStepKind(step.kind);
return (
<li key={i} className="break-words">
<span
title={step.config_hint}
className="ml-1.5 inline-block max-w-full truncate rounded bg-surface-subtle px-1 py-px align-middle font-mono text-[11px] text-content-muted">
{step.config_hint}
data-testid="workflow-proposal-step-kind"
className="mr-1.5 inline-block rounded-full bg-ocean-100 px-1.5 py-0.5 text-[10px] font-medium text-ocean-700 dark:bg-ocean-500/15 dark:text-ocean-300">
{kindLabel}
</span>
) : null}
</li>
))}
<span>{step.name}</span>
</li>
);
})}
</ol>
) : (
<p className="mt-1 text-xs text-content-faint">{t('chat.flowProposal.noSteps')}</p>
+11
View File
@@ -3415,6 +3415,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': 'تعذّر حفظ سير العمل. حاول مرة أخرى.',
'chat.flowProposal.enableError':
'تم حفظ سير العمل، لكن تعذّر تفعيله. حاول مرة أخرى، أو فعّله من صفحة سير العمل.',
'chat.flowProposal.stepKind.agent': 'وكيل',
'chat.flowProposal.stepKind.toolCall': 'إجراء',
'chat.flowProposal.stepKind.httpRequest': 'طلب ويب',
'chat.flowProposal.stepKind.code': 'تشغيل الكود',
'chat.flowProposal.stepKind.condition': 'شرط',
'chat.flowProposal.stepKind.switch': 'تبديل',
'chat.flowProposal.stepKind.merge': 'دمج',
'chat.flowProposal.stepKind.splitOut': 'تقسيم',
'chat.flowProposal.stepKind.transform': 'تحويل',
'chat.flowProposal.stepKind.outputParser': 'تحليل الإخراج',
'chat.flowProposal.stepKind.subWorkflow': 'مسار عمل فرعي',
'channels.authMode.managed_dm': 'قم بتسجيل الدخول باستخدام OpenHuman',
'channels.authMode.oauth': 'OAuth تسجيل الدخول',
'channels.authMode.bot_token': 'استخدم رمز الروبوت الخاص بك',
+11
View File
@@ -3494,6 +3494,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': 'ওয়ার্কফ্লো সংরক্ষণ করা যায়নি। আবার চেষ্টা করুন।',
'chat.flowProposal.enableError':
'ওয়ার্কফ্লো সংরক্ষিত হয়েছে, কিন্তু সক্ষম করা যায়নি। আবার চেষ্টা করুন, বা Workflows পৃষ্ঠা থেকে সক্ষম করুন।',
'chat.flowProposal.stepKind.agent': 'এজেন্ট',
'chat.flowProposal.stepKind.toolCall': 'কার্যক্রম',
'chat.flowProposal.stepKind.httpRequest': 'ওয়েব অনুরোধ',
'chat.flowProposal.stepKind.code': 'কোড চালান',
'chat.flowProposal.stepKind.condition': 'শর্ত',
'chat.flowProposal.stepKind.switch': 'সুইচ',
'chat.flowProposal.stepKind.merge': 'একত্রিত করুন',
'chat.flowProposal.stepKind.splitOut': 'বিভক্ত করুন',
'chat.flowProposal.stepKind.transform': 'রূপান্তর করুন',
'chat.flowProposal.stepKind.outputParser': 'আউটপুট পার্স করুন',
'chat.flowProposal.stepKind.subWorkflow': 'সাব-ওয়ার্কফ্লো',
'channels.authMode.managed_dm': 'OpenHuman দিয়ে লগইন করুন',
'channels.authMode.oauth': 'OAuth সাইন-ইন করুন',
'channels.authMode.bot_token': 'আপনার নিজের বট টোকেন ব্যবহার করুন',
+11
View File
@@ -3596,6 +3596,17 @@ const messages: TranslationMap = {
'Der Workflow konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.',
'chat.flowProposal.enableError':
'Workflow gespeichert, konnte aber nicht aktiviert werden. Versuchen Sie es erneut oder aktivieren Sie ihn auf der Workflows-Seite.',
'chat.flowProposal.stepKind.agent': 'Agent',
'chat.flowProposal.stepKind.toolCall': 'Aktion',
'chat.flowProposal.stepKind.httpRequest': 'Webanfrage',
'chat.flowProposal.stepKind.code': 'Code ausführen',
'chat.flowProposal.stepKind.condition': 'Bedingung',
'chat.flowProposal.stepKind.switch': 'Weiche',
'chat.flowProposal.stepKind.merge': 'Zusammenführen',
'chat.flowProposal.stepKind.splitOut': 'Aufteilen',
'chat.flowProposal.stepKind.transform': 'Transformieren',
'chat.flowProposal.stepKind.outputParser': 'Ausgabe auswerten',
'chat.flowProposal.stepKind.subWorkflow': 'Unter-Workflow',
'channels.authMode.managed_dm': 'Mit OpenHuman anmelden',
'channels.authMode.oauth': 'OAuth-Anmeldung',
'channels.authMode.bot_token': 'Eigenen Bot-Token verwenden',
+14
View File
@@ -3899,6 +3899,20 @@ const en: TranslationMap = {
'chat.flowProposal.error': 'Could not save the workflow. Please try again.',
'chat.flowProposal.enableError':
'Workflow saved, but could not enable it. Try again, or enable it from the Workflows page.',
// Plain-language labels for each `tinyflows` node kind, shown as the badge
// next to each step in the proposal card's step list. Keep names short:
// they render as small pill badges.
'chat.flowProposal.stepKind.agent': 'Agent',
'chat.flowProposal.stepKind.toolCall': 'Action',
'chat.flowProposal.stepKind.httpRequest': 'Web request',
'chat.flowProposal.stepKind.code': 'Run code',
'chat.flowProposal.stepKind.condition': 'Condition',
'chat.flowProposal.stepKind.switch': 'Switch',
'chat.flowProposal.stepKind.merge': 'Merge',
'chat.flowProposal.stepKind.splitOut': 'Split',
'chat.flowProposal.stepKind.transform': 'Transform',
'chat.flowProposal.stepKind.outputParser': 'Parse output',
'chat.flowProposal.stepKind.subWorkflow': 'Sub-workflow',
// Auth mode labels
'channels.authMode.managed_dm': 'Login with OpenHuman',
+11
View File
@@ -3558,6 +3558,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': 'No se pudo guardar el flujo de trabajo. Inténtalo de nuevo.',
'chat.flowProposal.enableError':
'Flujo de trabajo guardado, pero no se pudo activar. Inténtalo de nuevo o actívalo desde la página de Workflows.',
'chat.flowProposal.stepKind.agent': 'Agente',
'chat.flowProposal.stepKind.toolCall': 'Acción',
'chat.flowProposal.stepKind.httpRequest': 'Solicitud web',
'chat.flowProposal.stepKind.code': 'Ejecutar código',
'chat.flowProposal.stepKind.condition': 'Condición',
'chat.flowProposal.stepKind.switch': 'Selector',
'chat.flowProposal.stepKind.merge': 'Combinar',
'chat.flowProposal.stepKind.splitOut': 'Dividir',
'chat.flowProposal.stepKind.transform': 'Transformar',
'chat.flowProposal.stepKind.outputParser': 'Interpretar resultado',
'chat.flowProposal.stepKind.subWorkflow': 'Subflujo de trabajo',
'channels.authMode.managed_dm': 'Iniciar sesión con OpenHuman',
'channels.authMode.oauth': 'OAuth Iniciar sesión',
'channels.authMode.bot_token': 'Utilice su propio token de bot',
+11
View File
@@ -3581,6 +3581,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': "Impossible d'enregistrer le workflow. Veuillez réessayer.",
'chat.flowProposal.enableError':
"Workflow enregistré, mais impossible de l'activer. Réessayez, ou activez-le depuis la page Workflows.",
'chat.flowProposal.stepKind.agent': 'Agent',
'chat.flowProposal.stepKind.toolCall': 'Action',
'chat.flowProposal.stepKind.httpRequest': 'Requête web',
'chat.flowProposal.stepKind.code': 'Exécuter du code',
'chat.flowProposal.stepKind.condition': 'Condition',
'chat.flowProposal.stepKind.switch': 'Sélecteur',
'chat.flowProposal.stepKind.merge': 'Fusionner',
'chat.flowProposal.stepKind.splitOut': 'Diviser',
'chat.flowProposal.stepKind.transform': 'Transformer',
'chat.flowProposal.stepKind.outputParser': 'Analyser le résultat',
'chat.flowProposal.stepKind.subWorkflow': 'Sous-workflow',
'channels.authMode.managed_dm': 'Connectez-vous avec OpenHuman',
'channels.authMode.oauth': 'OAuth Connectez-vous',
'channels.authMode.bot_token': 'Utiliser votre propre jeton de robot',
+11
View File
@@ -3493,6 +3493,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': 'वर्कफ़्लो सहेजा नहीं जा सका। कृपया फिर से प्रयास करें।',
'chat.flowProposal.enableError':
'वर्कफ़्लो सहेजा गया, लेकिन सक्षम नहीं किया जा सका। फिर से प्रयास करें, या Workflows पेज से इसे सक्षम करें।',
'chat.flowProposal.stepKind.agent': 'एजेंट',
'chat.flowProposal.stepKind.toolCall': 'कार्रवाई',
'chat.flowProposal.stepKind.httpRequest': 'वेब अनुरोध',
'chat.flowProposal.stepKind.code': 'कोड चलाएं',
'chat.flowProposal.stepKind.condition': 'शर्त',
'chat.flowProposal.stepKind.switch': 'स्विच',
'chat.flowProposal.stepKind.merge': 'मर्ज करें',
'chat.flowProposal.stepKind.splitOut': 'विभाजित करें',
'chat.flowProposal.stepKind.transform': 'रूपांतरित करें',
'chat.flowProposal.stepKind.outputParser': 'आउटपुट पार्स करें',
'chat.flowProposal.stepKind.subWorkflow': 'सब-वर्कफ़्लो',
'channels.authMode.managed_dm': 'OpenHuman से लॉगिन करें',
'channels.authMode.oauth': 'OAuth साइन-इन करें',
'channels.authMode.bot_token': 'अपने स्वयं के बॉट टोकन का उपयोग करें',
+11
View File
@@ -3510,6 +3510,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': 'Alur kerja tidak dapat disimpan. Silakan coba lagi.',
'chat.flowProposal.enableError':
'Alur kerja disimpan, tetapi tidak dapat diaktifkan. Coba lagi, atau aktifkan dari halaman Workflows.',
'chat.flowProposal.stepKind.agent': 'Agen',
'chat.flowProposal.stepKind.toolCall': 'Tindakan',
'chat.flowProposal.stepKind.httpRequest': 'Permintaan web',
'chat.flowProposal.stepKind.code': 'Jalankan kode',
'chat.flowProposal.stepKind.condition': 'Kondisi',
'chat.flowProposal.stepKind.switch': 'Pengalih',
'chat.flowProposal.stepKind.merge': 'Gabungkan',
'chat.flowProposal.stepKind.splitOut': 'Pisahkan',
'chat.flowProposal.stepKind.transform': 'Transformasikan',
'chat.flowProposal.stepKind.outputParser': 'Uraikan output',
'chat.flowProposal.stepKind.subWorkflow': 'Sub-alur kerja',
'channels.authMode.managed_dm': 'Masuk dengan OpenHuman',
'channels.authMode.oauth': 'OAuth Masuk',
'channels.authMode.bot_token': 'Gunakan Token Bot Anda sendiri',
+11
View File
@@ -3557,6 +3557,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': 'Impossibile salvare il workflow. Riprova.',
'chat.flowProposal.enableError':
'Workflow salvato, ma non è stato possibile attivarlo. Riprova, oppure attivalo dalla pagina Workflows.',
'chat.flowProposal.stepKind.agent': 'Agente',
'chat.flowProposal.stepKind.toolCall': 'Azione',
'chat.flowProposal.stepKind.httpRequest': 'Richiesta web',
'chat.flowProposal.stepKind.code': 'Esegui codice',
'chat.flowProposal.stepKind.condition': 'Condizione',
'chat.flowProposal.stepKind.switch': 'Selettore',
'chat.flowProposal.stepKind.merge': 'Unisci',
'chat.flowProposal.stepKind.splitOut': 'Dividi',
'chat.flowProposal.stepKind.transform': 'Trasforma',
'chat.flowProposal.stepKind.outputParser': 'Analizza risultato',
'chat.flowProposal.stepKind.subWorkflow': 'Sotto-workflow',
'channels.authMode.managed_dm': 'Accedi con OpenHuman',
'channels.authMode.oauth': 'OAuth Accedi',
'channels.authMode.bot_token': 'Usa il tuo token Bot',
+11
View File
@@ -3458,6 +3458,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': '워크플로를 저장할 수 없습니다. 다시 시도하세요.',
'chat.flowProposal.enableError':
'워크플로는 저장되었지만 활성화할 수 없습니다. 다시 시도하거나 Workflows 페이지에서 활성화하세요.',
'chat.flowProposal.stepKind.agent': '에이전트',
'chat.flowProposal.stepKind.toolCall': '작업',
'chat.flowProposal.stepKind.httpRequest': '웹 요청',
'chat.flowProposal.stepKind.code': '코드 실행',
'chat.flowProposal.stepKind.condition': '조건',
'chat.flowProposal.stepKind.switch': '스위치',
'chat.flowProposal.stepKind.merge': '병합',
'chat.flowProposal.stepKind.splitOut': '분할',
'chat.flowProposal.stepKind.transform': '변환',
'chat.flowProposal.stepKind.outputParser': '출력 파싱',
'chat.flowProposal.stepKind.subWorkflow': '하위 워크플로',
'channels.authMode.managed_dm': 'OpenHuman로 로그인',
'channels.authMode.oauth': 'OAuth 로그인',
'channels.authMode.bot_token': '자체 봇 토큰 사용',
+11
View File
@@ -3538,6 +3538,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': 'Nie udało się zapisać przepływu pracy. Spróbuj ponownie.',
'chat.flowProposal.enableError':
'Przepływ pracy zapisany, ale nie udało się go włączyć. Spróbuj ponownie lub włącz go na stronie Workflows.',
'chat.flowProposal.stepKind.agent': 'Agent',
'chat.flowProposal.stepKind.toolCall': 'Akcja',
'chat.flowProposal.stepKind.httpRequest': 'Żądanie sieciowe',
'chat.flowProposal.stepKind.code': 'Uruchom kod',
'chat.flowProposal.stepKind.condition': 'Warunek',
'chat.flowProposal.stepKind.switch': 'Przełącznik',
'chat.flowProposal.stepKind.merge': 'Scal',
'chat.flowProposal.stepKind.splitOut': 'Podziel',
'chat.flowProposal.stepKind.transform': 'Przekształć',
'chat.flowProposal.stepKind.outputParser': 'Przetwórz wynik',
'chat.flowProposal.stepKind.subWorkflow': 'Podprzepływ pracy',
'channels.authMode.managed_dm': 'Zaloguj się z OpenHuman',
'channels.authMode.oauth': 'Logowanie OAuth',
'channels.authMode.bot_token': 'Użyj własnego tokena bota',
+11
View File
@@ -3550,6 +3550,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': 'Não foi possível salvar o fluxo de trabalho. Tente novamente.',
'chat.flowProposal.enableError':
'Fluxo de trabalho salvo, mas não foi possível ativá-lo. Tente novamente ou ative-o na página Workflows.',
'chat.flowProposal.stepKind.agent': 'Agente',
'chat.flowProposal.stepKind.toolCall': 'Ação',
'chat.flowProposal.stepKind.httpRequest': 'Pedido web',
'chat.flowProposal.stepKind.code': 'Executar código',
'chat.flowProposal.stepKind.condition': 'Condição',
'chat.flowProposal.stepKind.switch': 'Seletor',
'chat.flowProposal.stepKind.merge': 'Mesclar',
'chat.flowProposal.stepKind.splitOut': 'Dividir',
'chat.flowProposal.stepKind.transform': 'Transformar',
'chat.flowProposal.stepKind.outputParser': 'Analisar resultado',
'chat.flowProposal.stepKind.subWorkflow': 'Subfluxo de trabalho',
'channels.authMode.managed_dm': 'Faça login com OpenHuman',
'channels.authMode.oauth': 'OAuth Faça login',
'channels.authMode.bot_token': 'Use seu próprio token de bot',
+11
View File
@@ -3522,6 +3522,17 @@ const messages: TranslationMap = {
'chat.flowProposal.error': 'Не удалось сохранить рабочий процесс. Попробуйте еще раз.',
'chat.flowProposal.enableError':
'Рабочий процесс сохранён, но не удалось его включить. Попробуйте ещё раз или включите его на странице Workflows.',
'chat.flowProposal.stepKind.agent': 'Агент',
'chat.flowProposal.stepKind.toolCall': 'Действие',
'chat.flowProposal.stepKind.httpRequest': 'Веб-запрос',
'chat.flowProposal.stepKind.code': 'Выполнить код',
'chat.flowProposal.stepKind.condition': 'Условие',
'chat.flowProposal.stepKind.switch': 'Переключатель',
'chat.flowProposal.stepKind.merge': 'Объединить',
'chat.flowProposal.stepKind.splitOut': 'Разделить',
'chat.flowProposal.stepKind.transform': 'Преобразовать',
'chat.flowProposal.stepKind.outputParser': 'Обработать вывод',
'chat.flowProposal.stepKind.subWorkflow': 'Подпроцесс',
'channels.authMode.managed_dm': 'Войдите с помощью OpenHuman',
'channels.authMode.oauth': 'OAuth Вход в систему',
'channels.authMode.bot_token': 'Используйте свой собственный токен бота',
+11
View File
@@ -3313,6 +3313,17 @@ const messages: TranslationMap = {
'chat.flowProposal.dismiss': '忽略',
'chat.flowProposal.error': '无法保存该工作流。请重试。',
'chat.flowProposal.enableError': '工作流已保存,但无法启用。请重试,或在“工作流”页面手动启用。',
'chat.flowProposal.stepKind.agent': '智能体',
'chat.flowProposal.stepKind.toolCall': '操作',
'chat.flowProposal.stepKind.httpRequest': '网络请求',
'chat.flowProposal.stepKind.code': '运行代码',
'chat.flowProposal.stepKind.condition': '条件',
'chat.flowProposal.stepKind.switch': '分支选择',
'chat.flowProposal.stepKind.merge': '合并',
'chat.flowProposal.stepKind.splitOut': '拆分',
'chat.flowProposal.stepKind.transform': '转换',
'chat.flowProposal.stepKind.outputParser': '解析输出',
'chat.flowProposal.stepKind.subWorkflow': '子工作流',
'channels.authMode.managed_dm': '使用 OpenHuman 登录',
'channels.authMode.oauth': 'OAuth 登录',
'channels.authMode.bot_token': '使用你自己的 Bot Token',