Spaces:
Sleeping
Sleeping
| 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); | |