nodes-ui-flow / src /nodes /SemanticBranchFlowNode.jsx
markitzeroo
Deploy updated nodes UI flow
1dd9186
import { memo, useEffect } from 'react';
import { useUpdateNodeInternals } from '@xyflow/react';
import NodeShell from '../components/NodeShell.jsx';
import { NodeDraftInput, NodeDraftTextarea } from '../components/NodeDraftField.jsx';
import { useWorkflow } from '../context/WorkflowContext.jsx';
import { createBrowserId } from '../lib/ids.js';
import { getNodeAccent } from '../lib/nodeRegistry.js';
function SemanticBranchFlowNode({ id, data, selected, type }) {
const { getNodeHandles, replaceNodeData, removeHandleConnections } = useWorkflow();
const updateNodeInternals = useUpdateNodeInternals();
const handles = getNodeHandles(type, data);
const runtime = data.runtime || {};
useEffect(() => {
updateNodeInternals(id);
}, [id, data.choices.length, updateNodeInternals]);
const addChoice = () => {
replaceNodeData(id, (current) => ({
...current,
choices: [...current.choices, { id: createBrowserId('choice'), label: '' }],
}));
};
const updateChoice = (choiceId, label) => {
replaceNodeData(id, (current) => ({
...current,
choices: current.choices.map((choice) =>
choice.id === choiceId && choice.label !== label ? { ...choice, label } : choice,
),
}));
};
const removeChoice = (choiceId) => {
if (data.choices.length <= 1) {
return;
}
removeHandleConnections(id, choiceId, 'source');
replaceNodeData(id, (current) => ({
...current,
choices: current.choices.filter((choice) => choice.id !== choiceId),
}));
};
const matchedChoice = data.choices.find((choice) => choice.id === runtime.matchId);
return (
<NodeShell
nodeId={id}
title={data.title}
accent={getNodeAccent(type)}
selected={selected}
status={runtime.status}
inputs={handles.inputs}
outputs={handles.outputs}
>
<div className="field-stack">
<div className="condition-list">
{data.choices.map((choice, index) => (
<div key={choice.id} className="condition-row">
<NodeDraftInput
className="nodrag node-input"
type="text"
value={choice.label}
placeholder={`вариант ${index + 1}`}
onCommit={(value) => updateChoice(choice.id, value)}
/>
<button
type="button"
className="nodrag chip__remove"
onClick={() => removeChoice(choice.id)}
disabled={data.choices.length <= 1}
>
x
</button>
</div>
))}
</div>
<button type="button" className="nodrag node-button node-button--ghost" onClick={addChoice}>
+ Добавить вариант
</button>
<div className="node-note">
Используйте точку с запятой для альтернатив внутри одного варианта: да; хочу еще; давай.
</div>
<label className="field-row semantic-branch__retry-toggle">
<input
className="nodrag"
type="checkbox"
checked={data.retryOnUnclear !== false}
onChange={(event) =>
replaceNodeData(id, (current) => ({
...current,
retryOnUnclear: event.target.checked,
}))
}
/>
<span>Переспрашивать при unclear</span>
</label>
{data.retryOnUnclear !== false ? (
<>
<NodeDraftTextarea
className="nodrag nowheel node-textarea"
value={data.retryQuestion || ''}
placeholder="Вопрос для повтора..."
style={{ minHeight: '70px' }}
onCommit={(value) =>
replaceNodeData(id, (current) => ({
...current,
retryQuestion: value,
}))
}
/>
<label className="field-row">
<input
className="nodrag"
type="checkbox"
checked={Boolean(data.retryParaphrase)}
onChange={(event) =>
replaceNodeData(id, (current) => ({
...current,
retryParaphrase: event.target.checked,
}))
}
/>
<span>Перефразировать unclear-вопрос</span>
</label>
</>
) : null}
<div className="node-note">
{runtime.result
? `Классификация: ${runtime.result}${matchedChoice ? ` -> ${matchedChoice.label}` : ''}`
: 'LLM классифицирует ответ и активирует один выход.'}
</div>
{runtime.error ? <div className="node-error">{runtime.error}</div> : null}
</div>
</NodeShell>
);
}
export default memo(SemanticBranchFlowNode);