File size: 5,055 Bytes
1dd9186
cfaaa6c
 
1dd9186
cfaaa6c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1dd9186
cfaaa6c
1dd9186
cfaaa6c
 
 
 
1dd9186
cfaaa6c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1dd9186
cfaaa6c
1dd9186
cfaaa6c
 
1dd9186
 
 
 
 
 
cfaaa6c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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);