i18n(automation): localize RuleEditor and WorkflowEditor form strings
Adds ~40 automation.ruleEditor.* and automation.workflowEditor.* keys to en.ts + zh.ts (labels, placeholders, error messages, action labels). RuleFormModal and WorkflowFormModal now route every hardcoded English string through useT(), including validation errors and modal titles. Closes remaining Z-B i18n work after the prompt-builder locale dispatch (cf92e0c), dept-keywords typing (a78bdb9), and sidebar/ formatters/notification/skills i18n landed earlier.
This commit is contained in:
1 parent
a78bdb9b02
commit
1f310a121e
4 files changed
+115
-29
No files matched your search
@@ -3,40 +3,42 @@ import { Gear, Lightning } from "../ui/icons"
|
||||
import type { AutomationAction, AutomationRuleRecord, AutomationTrigger, DepartmentId } from "@shared/types"
|
||||
import { ActionButton, Modal, Select } from "../ui"
|
||||
import { triggerConfig, actionConfig, departmentOptions, safeJsonStringify } from "./automation-config"
|
||||
import { useT } from "../../i18n"
|
||||
|
||||
interface RuleFormState { name: string; description: string; trigger: AutomationTrigger; conditionsJson: string; action: AutomationAction; actionConfigJson: string; sourceDepartment: string; targetDepartment: string; priority: number }
|
||||
const defaultRuleForm: RuleFormState = { name: "", description: "", trigger: "task_status_changed", conditionsJson: "{}", action: "assign_task", actionConfigJson: "{}", sourceDepartment: "", targetDepartment: "", priority: 50 }
|
||||
function ruleToFormState(rule: AutomationRuleRecord): RuleFormState { return { name: rule.name, description: rule.description, trigger: rule.trigger, conditionsJson: safeJsonStringify(rule.conditionsJson), action: rule.action, actionConfigJson: safeJsonStringify(rule.actionConfigJson), sourceDepartment: rule.sourceDepartment ?? "", targetDepartment: rule.targetDepartment ?? "", priority: rule.priority } }
|
||||
|
||||
export function RuleFormModal({ open, editingRule, companyId, onClose, onSaved }: { open: boolean; editingRule: AutomationRuleRecord | null; companyId: string; onClose: () => void; onSaved: () => void }) {
|
||||
const t = useT()
|
||||
const isEditing = editingRule !== null; const [form, setForm] = useState<RuleFormState>(defaultRuleForm); const [saving, setSaving] = useState(false); const [error, setError] = useState<string | null>(null)
|
||||
useEffect(() => { if (open) { setForm(editingRule ? ruleToFormState(editingRule) : defaultRuleForm); setError(null) } }, [open, editingRule])
|
||||
const updateField = useCallback(<K extends keyof RuleFormState>(key: K, value: RuleFormState[K]) => { setForm((prev) => ({ ...prev, [key]: value })) }, [])
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!form.name.trim()) { setError("Rule name is required"); return }
|
||||
try { JSON.parse(form.conditionsJson) } catch { setError("Conditions must be valid JSON"); return }
|
||||
try { JSON.parse(form.actionConfigJson) } catch { setError("Action config must be valid JSON"); return }
|
||||
if (!form.name.trim()) { setError(t("automation.ruleEditor.errNameRequired")); return }
|
||||
try { JSON.parse(form.conditionsJson) } catch { setError(t("automation.ruleEditor.errConditionsJson")); return }
|
||||
try { JSON.parse(form.actionConfigJson) } catch { setError(t("automation.ruleEditor.errActionConfigJson")); return }
|
||||
setSaving(true); setError(null)
|
||||
try { const result = await window.agentCompany.saveAutomationRule({ id: editingRule?.id ?? null, companyId, name: form.name.trim(), description: form.description.trim(), trigger: form.trigger, conditionsJson: form.conditionsJson, action: form.action, actionConfigJson: form.actionConfigJson, sourceDepartment: (form.sourceDepartment || null) as DepartmentId | null, targetDepartment: (form.targetDepartment || null) as DepartmentId | null, priority: form.priority, status: editingRule?.status ?? "active" }); if (!result.ok) { setError(result.error.message); return }; onSaved(); onClose() } catch (err) { setError(err instanceof Error ? err.message : "Failed to save rule") } finally { setSaving(false) }
|
||||
}, [form, editingRule, companyId, onSaved, onClose])
|
||||
try { const result = await window.agentCompany.saveAutomationRule({ id: editingRule?.id ?? null, companyId, name: form.name.trim(), description: form.description.trim(), trigger: form.trigger, conditionsJson: form.conditionsJson, action: form.action, actionConfigJson: form.actionConfigJson, sourceDepartment: (form.sourceDepartment || null) as DepartmentId | null, targetDepartment: (form.targetDepartment || null) as DepartmentId | null, priority: form.priority, status: editingRule?.status ?? "active" }); if (!result.ok) { setError(result.error.message); return }; onSaved(); onClose() } catch (err) { setError(err instanceof Error ? err.message : t("automation.ruleEditor.errSaveFailed")) } finally { setSaving(false) }
|
||||
}, [form, editingRule, companyId, onSaved, onClose, t])
|
||||
const triggerOptions = Object.entries(triggerConfig).map(([key, c]) => ({ value: key, label: c.label })); const actionOptions = Object.entries(actionConfig).map(([key, c]) => ({ value: key, label: c.label }))
|
||||
const selectedTriggerInfo = triggerConfig[form.trigger]; const selectedActionInfo = actionConfig[form.action]
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} title={isEditing ? "Edit Automation Rule" : "New Automation Rule"} width="580px">
|
||||
<Modal open={open} onClose={onClose} title={isEditing ? t("automation.ruleEditor.editTitle") : t("automation.ruleEditor.newTitle")} width="580px">
|
||||
<div className="space-y-5">
|
||||
{error ? <div className="rounded-lg bg-destructive-muted px-3.5 py-2.5 text-[12px] font-medium text-destructive">{error}</div> : null}
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Rule Name</label><input type="text" value={form.name} onChange={(e) => updateField("name", e.target.value)} placeholder="e.g., Auto-assign blocked tasks" className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 text-[14px] text-foreground placeholder:text-muted-foreground" /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Description</label><textarea value={form.description} onChange={(e) => updateField("description", e.target.value)} placeholder="What does this rule do?" rows={2} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 text-[14px] text-foreground placeholder:text-muted-foreground" /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Trigger</label><Select value={form.trigger} onChange={(v) => updateField("trigger", v as AutomationTrigger)} options={triggerOptions} />{selectedTriggerInfo ? <div className="mt-1.5 text-[11px] text-muted-foreground">{selectedTriggerInfo.description}</div> : null}</div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Conditions (JSON)</label><textarea value={form.conditionsJson} onChange={(e) => updateField("conditionsJson", e.target.value)} rows={3} spellCheck={false} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 font-mono text-[12px] text-foreground" placeholder='{"status": "blocked"}' /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Action</label><Select value={form.action} onChange={(v) => updateField("action", v as AutomationAction)} options={actionOptions} />{selectedActionInfo ? <div className="mt-1.5 text-[11px] text-muted-foreground">{selectedActionInfo.description}</div> : null}</div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Action Configuration (JSON)</label><textarea value={form.actionConfigJson} onChange={(e) => updateField("actionConfigJson", e.target.value)} rows={3} spellCheck={false} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 font-mono text-[12px] text-foreground" placeholder='{"targetAgentId": "..."}' /></div>
|
||||
<div className="grid grid-cols-2 gap-3"><div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Source Department</label><Select value={form.sourceDepartment} onChange={(v) => updateField("sourceDepartment", v)} options={departmentOptions} /></div><div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Target Department</label><Select value={form.targetDepartment} onChange={(v) => updateField("targetDepartment", v)} options={departmentOptions} /></div></div>
|
||||
<div><div className="mb-1.5 flex items-center justify-between"><label className="text-[12px] font-medium text-muted-foreground">Priority</label><span className="text-[12px] font-semibold text-primary">{form.priority}</span></div><input type="range" min={0} max={100} value={form.priority} onChange={(e) => updateField("priority", parseInt(e.target.value, 10))} className="w-full accent-[color:var(--primary)]" /><div className="mt-1 flex justify-between text-[10px] text-muted-foreground"><span>Low priority</span><span>High priority</span></div></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.nameLabel")}</label><input type="text" value={form.name} onChange={(e) => updateField("name", e.target.value)} placeholder={t("automation.ruleEditor.namePlaceholder")} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 text-[14px] text-foreground placeholder:text-muted-foreground" /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.descriptionLabel")}</label><textarea value={form.description} onChange={(e) => updateField("description", e.target.value)} placeholder={t("automation.ruleEditor.descriptionPlaceholder")} rows={2} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 text-[14px] text-foreground placeholder:text-muted-foreground" /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.triggerLabel")}</label><Select value={form.trigger} onChange={(v) => updateField("trigger", v as AutomationTrigger)} options={triggerOptions} />{selectedTriggerInfo ? <div className="mt-1.5 text-[11px] text-muted-foreground">{selectedTriggerInfo.description}</div> : null}</div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.conditionsLabel")}</label><textarea value={form.conditionsJson} onChange={(e) => updateField("conditionsJson", e.target.value)} rows={3} spellCheck={false} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 font-mono text-[12px] text-foreground" placeholder='{"status": "blocked"}' /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.actionLabel")}</label><Select value={form.action} onChange={(v) => updateField("action", v as AutomationAction)} options={actionOptions} />{selectedActionInfo ? <div className="mt-1.5 text-[11px] text-muted-foreground">{selectedActionInfo.description}</div> : null}</div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.actionConfigLabel")}</label><textarea value={form.actionConfigJson} onChange={(e) => updateField("actionConfigJson", e.target.value)} rows={3} spellCheck={false} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 font-mono text-[12px] text-foreground" placeholder='{"targetAgentId": "..."}' /></div>
|
||||
<div className="grid grid-cols-2 gap-3"><div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.sourceDepartmentLabel")}</label><Select value={form.sourceDepartment} onChange={(v) => updateField("sourceDepartment", v)} options={departmentOptions} /></div><div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.targetDepartmentLabel")}</label><Select value={form.targetDepartment} onChange={(v) => updateField("targetDepartment", v)} options={departmentOptions} /></div></div>
|
||||
<div><div className="mb-1.5 flex items-center justify-between"><label className="text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.priorityLabel")}</label><span className="text-[12px] font-semibold text-primary">{form.priority}</span></div><input type="range" min={0} max={100} value={form.priority} onChange={(e) => updateField("priority", parseInt(e.target.value, 10))} className="w-full accent-[color:var(--primary)]" /><div className="mt-1 flex justify-between text-[10px] text-muted-foreground"><span>{t("automation.ruleEditor.priorityLow")}</span><span>{t("automation.ruleEditor.priorityHigh")}</span></div></div>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end gap-3 border-t border-border pt-4">
|
||||
<ActionButton label="Cancel" onClick={onClose} />
|
||||
<ActionButton label={isEditing ? "Save Changes" : "Create Rule"} onClick={() => void handleSave()} icon={isEditing ? Gear : Lightning} tone="accent" loading={saving} disabled={!form.name.trim()} />
|
||||
<ActionButton label={t("automation.ruleEditor.cancel")} onClick={onClose} />
|
||||
<ActionButton label={isEditing ? t("automation.ruleEditor.save") : t("automation.ruleEditor.create")} onClick={() => void handleSave()} icon={isEditing ? Gear : Lightning} tone="accent" loading={saving} disabled={!form.name.trim()} />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -3,37 +3,39 @@ import { ArrowsClockwise, Gear } from "../ui/icons"
|
||||
import type { AutomationTrigger, WorkflowPipelineRecord, WorkflowStatus } from "@shared/types"
|
||||
import { ActionButton, Modal, Select } from "../ui"
|
||||
import { triggerConfig, safeJsonStringify } from "./automation-config"
|
||||
import { useT } from "../../i18n"
|
||||
|
||||
interface WorkflowFormState { name: string; description: string; triggerType: AutomationTrigger; triggerConfigJson: string; status: WorkflowStatus; stepsJson: string }
|
||||
const defaultWorkflowForm: WorkflowFormState = { name: "", description: "", triggerType: "task_created", triggerConfigJson: "{}", status: "draft", stepsJson: "[]" }
|
||||
function workflowToFormState(wf: WorkflowPipelineRecord): WorkflowFormState { return { name: wf.name, description: wf.description, triggerType: wf.triggerType, triggerConfigJson: safeJsonStringify(wf.triggerConfigJson), status: wf.status, stepsJson: safeJsonStringify(wf.stepsJson) } }
|
||||
|
||||
export function WorkflowFormModal({ open, editingWorkflow, companyId, onClose, onSaved }: { open: boolean; editingWorkflow: WorkflowPipelineRecord | null; companyId: string; onClose: () => void; onSaved: () => void }) {
|
||||
const t = useT()
|
||||
const isEditing = editingWorkflow !== null; const [form, setForm] = useState<WorkflowFormState>(defaultWorkflowForm); const [saving, setSaving] = useState(false); const [error, setError] = useState<string | null>(null)
|
||||
useEffect(() => { if (open) { setForm(editingWorkflow ? workflowToFormState(editingWorkflow) : defaultWorkflowForm); setError(null) } }, [open, editingWorkflow])
|
||||
const updateField = useCallback(<K extends keyof WorkflowFormState>(key: K, value: WorkflowFormState[K]) => { setForm((p) => ({ ...p, [key]: value })) }, [])
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!form.name.trim()) { setError("Workflow name is required"); return }
|
||||
try { JSON.parse(form.triggerConfigJson) } catch { setError("Trigger config must be valid JSON"); return }
|
||||
try { JSON.parse(form.stepsJson) } catch { setError("Steps must be valid JSON array"); return }
|
||||
if (!form.name.trim()) { setError(t("automation.workflowEditor.errNameRequired")); return }
|
||||
try { JSON.parse(form.triggerConfigJson) } catch { setError(t("automation.workflowEditor.errTriggerJson")); return }
|
||||
try { JSON.parse(form.stepsJson) } catch { setError(t("automation.workflowEditor.errStepsJson")); return }
|
||||
setSaving(true); setError(null)
|
||||
try { const result = await window.agentCompany.saveWorkflow({ id: editingWorkflow?.id ?? null, companyId, name: form.name.trim(), description: form.description.trim(), triggerType: form.triggerType, triggerConfigJson: form.triggerConfigJson, status: form.status, stepsJson: form.stepsJson }); if (!result.ok) { setError(result.error.message); return }; onSaved(); onClose() } catch (err) { setError(err instanceof Error ? err.message : "Failed to save workflow") } finally { setSaving(false) }
|
||||
}, [form, editingWorkflow, companyId, onSaved, onClose])
|
||||
try { const result = await window.agentCompany.saveWorkflow({ id: editingWorkflow?.id ?? null, companyId, name: form.name.trim(), description: form.description.trim(), triggerType: form.triggerType, triggerConfigJson: form.triggerConfigJson, status: form.status, stepsJson: form.stepsJson }); if (!result.ok) { setError(result.error.message); return }; onSaved(); onClose() } catch (err) { setError(err instanceof Error ? err.message : t("automation.workflowEditor.errSaveFailed")) } finally { setSaving(false) }
|
||||
}, [form, editingWorkflow, companyId, onSaved, onClose, t])
|
||||
const triggerOptions = Object.entries(triggerConfig).map(([key, c]) => ({ value: key, label: c.label }))
|
||||
const statusOptions = [{ value: "draft", label: "Draft" }, { value: "active", label: "Active" }, { value: "paused", label: "Paused" }]
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} title={isEditing ? "Edit Workflow" : "New Workflow"} width="540px">
|
||||
<Modal open={open} onClose={onClose} title={isEditing ? t("automation.workflowEditor.editTitle") : t("automation.workflowEditor.newTitle")} width="540px">
|
||||
<div className="space-y-5">
|
||||
{error ? <div className="rounded-lg bg-destructive-muted px-3.5 py-2.5 text-[12px] font-medium text-destructive">{error}</div> : null}
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Workflow Name</label><input type="text" value={form.name} onChange={(e) => updateField("name", e.target.value)} placeholder="e.g., Bug Fix Pipeline" className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 text-[14px] text-foreground placeholder:text-muted-foreground" /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Description</label><textarea value={form.description} onChange={(e) => updateField("description", e.target.value)} placeholder="Describe this workflow pipeline" rows={2} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 text-[14px] text-foreground placeholder:text-muted-foreground" /></div>
|
||||
<div className="grid grid-cols-2 gap-3"><div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Trigger</label><Select value={form.triggerType} onChange={(v) => updateField("triggerType", v as AutomationTrigger)} options={triggerOptions} /></div><div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Status</label><Select value={form.status} onChange={(v) => updateField("status", v as WorkflowStatus)} options={statusOptions} /></div></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Trigger Configuration (JSON)</label><textarea value={form.triggerConfigJson} onChange={(e) => updateField("triggerConfigJson", e.target.value)} rows={2} spellCheck={false} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 font-mono text-[12px] text-foreground" placeholder="{}" /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Pipeline Steps (JSON Array)</label><textarea value={form.stepsJson} onChange={(e) => updateField("stepsJson", e.target.value)} rows={6} spellCheck={false} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 font-mono text-[12px] text-foreground" placeholder='[{"id":"step-1","name":"Assign","action":"assign_task","configJson":"{}","assigneeDepartment":"engineering","assigneeAgentId":null,"requiresApproval":false,"timeoutSec":0,"status":"pending"}]' /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.workflowEditor.nameLabel")}</label><input type="text" value={form.name} onChange={(e) => updateField("name", e.target.value)} placeholder={t("automation.workflowEditor.namePlaceholder")} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 text-[14px] text-foreground placeholder:text-muted-foreground" /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.workflowEditor.descriptionLabel")}</label><textarea value={form.description} onChange={(e) => updateField("description", e.target.value)} placeholder={t("automation.workflowEditor.descriptionPlaceholder")} rows={2} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 text-[14px] text-foreground placeholder:text-muted-foreground" /></div>
|
||||
<div className="grid grid-cols-2 gap-3"><div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.ruleEditor.triggerLabel")}</label><Select value={form.triggerType} onChange={(v) => updateField("triggerType", v as AutomationTrigger)} options={triggerOptions} /></div><div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">Status</label><Select value={form.status} onChange={(v) => updateField("status", v as WorkflowStatus)} options={statusOptions} /></div></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.workflowEditor.triggerLabel")}</label><textarea value={form.triggerConfigJson} onChange={(e) => updateField("triggerConfigJson", e.target.value)} rows={2} spellCheck={false} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 font-mono text-[12px] text-foreground" placeholder="{}" /></div>
|
||||
<div><label className="mb-1.5 block text-[12px] font-medium text-muted-foreground">{t("automation.workflowEditor.stepsLabel")}</label><textarea value={form.stepsJson} onChange={(e) => updateField("stepsJson", e.target.value)} rows={6} spellCheck={false} className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border border-border bg-card px-3.5 py-2.5 font-mono text-[12px] text-foreground" placeholder='[{"id":"step-1","name":"Assign","action":"assign_task","configJson":"{}","assigneeDepartment":"engineering","assigneeAgentId":null,"requiresApproval":false,"timeoutSec":0,"status":"pending"}]' /></div>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end gap-3 border-t border-border pt-4">
|
||||
<ActionButton label="Cancel" onClick={onClose} />
|
||||
<ActionButton label={isEditing ? "Save Changes" : "Create Workflow"} onClick={() => void handleSave()} icon={isEditing ? Gear : ArrowsClockwise} tone="accent" loading={saving} disabled={!form.name.trim()} />
|
||||
<ActionButton label={t("automation.workflowEditor.cancel")} onClick={onClose} />
|
||||
<ActionButton label={isEditing ? t("automation.workflowEditor.save") : t("automation.workflowEditor.create")} onClick={() => void handleSave()} icon={isEditing ? Gear : ArrowsClockwise} tone="accent" loading={saving} disabled={!form.name.trim()} />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -935,4 +935,45 @@ export const en: Record<string, string> = {
|
||||
"conn.refreshingState": "Refreshing connector state...",
|
||||
"conn.latestHealthCheck": "Latest health-check detail",
|
||||
"conn.configDoc": "Configuration doc",
|
||||
|
||||
// ── Automation rule editor ──
|
||||
"automation.ruleEditor.newTitle": "New Automation Rule",
|
||||
"automation.ruleEditor.editTitle": "Edit Automation Rule",
|
||||
"automation.ruleEditor.nameLabel": "Rule Name",
|
||||
"automation.ruleEditor.namePlaceholder": "e.g., Auto-assign blocked tasks",
|
||||
"automation.ruleEditor.descriptionLabel": "Description",
|
||||
"automation.ruleEditor.descriptionPlaceholder": "What does this rule do?",
|
||||
"automation.ruleEditor.triggerLabel": "Trigger",
|
||||
"automation.ruleEditor.conditionsLabel": "Conditions (JSON)",
|
||||
"automation.ruleEditor.actionLabel": "Action",
|
||||
"automation.ruleEditor.actionConfigLabel": "Action Configuration (JSON)",
|
||||
"automation.ruleEditor.sourceDepartmentLabel": "Source Department",
|
||||
"automation.ruleEditor.targetDepartmentLabel": "Target Department",
|
||||
"automation.ruleEditor.priorityLabel": "Priority",
|
||||
"automation.ruleEditor.priorityLow": "Low priority",
|
||||
"automation.ruleEditor.priorityHigh": "High priority",
|
||||
"automation.ruleEditor.cancel": "Cancel",
|
||||
"automation.ruleEditor.save": "Save Changes",
|
||||
"automation.ruleEditor.create": "Create Rule",
|
||||
"automation.ruleEditor.errNameRequired": "Rule name is required",
|
||||
"automation.ruleEditor.errConditionsJson": "Conditions must be valid JSON",
|
||||
"automation.ruleEditor.errActionConfigJson": "Action config must be valid JSON",
|
||||
"automation.ruleEditor.errSaveFailed": "Failed to save rule",
|
||||
|
||||
// ── Workflow editor ──
|
||||
"automation.workflowEditor.newTitle": "New Workflow",
|
||||
"automation.workflowEditor.editTitle": "Edit Workflow",
|
||||
"automation.workflowEditor.nameLabel": "Workflow Name",
|
||||
"automation.workflowEditor.namePlaceholder": "e.g., Production deployment pipeline",
|
||||
"automation.workflowEditor.descriptionLabel": "Description",
|
||||
"automation.workflowEditor.descriptionPlaceholder": "Describe this workflow pipeline",
|
||||
"automation.workflowEditor.triggerLabel": "Trigger (JSON)",
|
||||
"automation.workflowEditor.stepsLabel": "Steps (JSON array)",
|
||||
"automation.workflowEditor.cancel": "Cancel",
|
||||
"automation.workflowEditor.save": "Save Changes",
|
||||
"automation.workflowEditor.create": "Create Workflow",
|
||||
"automation.workflowEditor.errNameRequired": "Workflow name is required",
|
||||
"automation.workflowEditor.errTriggerJson": "Trigger must be valid JSON",
|
||||
"automation.workflowEditor.errStepsJson": "Steps must be a valid JSON array",
|
||||
"automation.workflowEditor.errSaveFailed": "Failed to save workflow",
|
||||
};
|
||||
@@ -935,4 +935,45 @@ export const zh: Record<string, string> = {
|
||||
"conn.refreshingState": "正在刷新连接器状态...",
|
||||
"conn.latestHealthCheck": "最新健康检查详情",
|
||||
"conn.configDoc": "配置文档",
|
||||
|
||||
// ── 自动化规则编辑器 ──
|
||||
"automation.ruleEditor.newTitle": "新建自动化规则",
|
||||
"automation.ruleEditor.editTitle": "编辑自动化规则",
|
||||
"automation.ruleEditor.nameLabel": "规则名称",
|
||||
"automation.ruleEditor.namePlaceholder": "例如:自动分配阻塞任务",
|
||||
"automation.ruleEditor.descriptionLabel": "描述",
|
||||
"automation.ruleEditor.descriptionPlaceholder": "这条规则的作用?",
|
||||
"automation.ruleEditor.triggerLabel": "触发条件",
|
||||
"automation.ruleEditor.conditionsLabel": "条件 (JSON)",
|
||||
"automation.ruleEditor.actionLabel": "动作",
|
||||
"automation.ruleEditor.actionConfigLabel": "动作配置 (JSON)",
|
||||
"automation.ruleEditor.sourceDepartmentLabel": "来源部门",
|
||||
"automation.ruleEditor.targetDepartmentLabel": "目标部门",
|
||||
"automation.ruleEditor.priorityLabel": "优先级",
|
||||
"automation.ruleEditor.priorityLow": "低优先级",
|
||||
"automation.ruleEditor.priorityHigh": "高优先级",
|
||||
"automation.ruleEditor.cancel": "取消",
|
||||
"automation.ruleEditor.save": "保存更改",
|
||||
"automation.ruleEditor.create": "创建规则",
|
||||
"automation.ruleEditor.errNameRequired": "请填写规则名称",
|
||||
"automation.ruleEditor.errConditionsJson": "条件必须是有效的 JSON",
|
||||
"automation.ruleEditor.errActionConfigJson": "动作配置必须是有效的 JSON",
|
||||
"automation.ruleEditor.errSaveFailed": "保存规则失败",
|
||||
|
||||
// ── 工作流编辑器 ──
|
||||
"automation.workflowEditor.newTitle": "新建工作流",
|
||||
"automation.workflowEditor.editTitle": "编辑工作流",
|
||||
"automation.workflowEditor.nameLabel": "工作流名称",
|
||||
"automation.workflowEditor.namePlaceholder": "例如:生产部署流水线",
|
||||
"automation.workflowEditor.descriptionLabel": "描述",
|
||||
"automation.workflowEditor.descriptionPlaceholder": "描述这个工作流流水线",
|
||||
"automation.workflowEditor.triggerLabel": "触发条件 (JSON)",
|
||||
"automation.workflowEditor.stepsLabel": "步骤 (JSON 数组)",
|
||||
"automation.workflowEditor.cancel": "取消",
|
||||
"automation.workflowEditor.save": "保存更改",
|
||||
"automation.workflowEditor.create": "创建工作流",
|
||||
"automation.workflowEditor.errNameRequired": "请填写工作流名称",
|
||||
"automation.workflowEditor.errTriggerJson": "触发条件必须是有效的 JSON",
|
||||
"automation.workflowEditor.errStepsJson": "步骤必须是有效的 JSON 数组",
|
||||
"automation.workflowEditor.errSaveFailed": "保存工作流失败",
|
||||
};
|
||||
Reference in new issue
Block a user