mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
synthesis
This commit is contained in:
parent
2f08cd4803
commit
73bbde3acb
|
@ -0,0 +1,69 @@
|
|||
# Generated by Django 5.0.5 on 2024-06-21 15:47
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rsform', '0007_location_and_flags'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InputNode',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('vertical_coordinate', models.IntegerField(verbose_name='Вертикальная координата звена')),
|
||||
('horizontal_coordinate', models.IntegerField(verbose_name='Горизонтальная координата звена')),
|
||||
('rsform_id', models.IntegerField(null=True, verbose_name='Схема')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SynthesisGraph',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('status', models.CharField(choices=[('Draft', 'Draft'), ('Completed', 'Completed'), ('Warning', 'Warning'), ('Failed', 'Failed')], max_length=20, verbose_name='Статус операции слияния')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OperationNode',
|
||||
fields=[
|
||||
('inputnode_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='rsform.inputnode')),
|
||||
('name', models.CharField(max_length=20, verbose_name='Название')),
|
||||
('status', models.CharField(choices=[('Draft', 'Draft'), ('Completed', 'Completed'), ('Warning', 'Warning'), ('Failed', 'Failed')], max_length=20, verbose_name='Статус операции слияния')),
|
||||
('left_parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rsform_library_item_left', to='rsform.libraryitem', verbose_name='Левый предок')),
|
||||
('right_parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rsform_library_item_right', to='rsform.libraryitem', verbose_name='Правый предок')),
|
||||
],
|
||||
bases=('rsform.inputnode',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SynthesisEdge',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('decoded_id', models.CharField(max_length=30, verbose_name='Id ребра на фронте')),
|
||||
('source_handle', models.CharField(max_length=30, verbose_name='')),
|
||||
('node_from', models.IntegerField(verbose_name='Звено-предок')),
|
||||
('node_to', models.IntegerField(verbose_name='Звено-наследник')),
|
||||
('graph_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.synthesisgraph', verbose_name='Схема синтеза')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inputnode',
|
||||
name='graph_id',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.synthesisgraph', verbose_name='Схема синтеза'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SynthesisSubstitution',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('deleteRight', models.BooleanField(verbose_name='Удалить правую')),
|
||||
('takeLeftTerm', models.BooleanField(verbose_name='Использовать термин левой')),
|
||||
('graph_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.synthesisgraph', verbose_name='Схема синтеза')),
|
||||
('leftCst', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='constituenta_original', to='rsform.constituenta', verbose_name='Конституента')),
|
||||
('rightCst', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='constituenta_substitution', to='rsform.constituenta', verbose_name='Подстановка')),
|
||||
('operation_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.operationnode', verbose_name='Операция синтеза')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -102,10 +102,6 @@ class LibraryItem(Model):
|
|||
auto_now=True
|
||||
)
|
||||
|
||||
#is_hidden: BooleanField = BooleanField(
|
||||
# default=False
|
||||
#)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Схема'
|
||||
|
|
|
@ -1,90 +1,139 @@
|
|||
from django.db.models import (
|
||||
CASCADE, SET_NULL, ForeignKey, Model, PositiveIntegerField, QuerySet,
|
||||
TextChoices, TextField, BooleanField, CharField, DateTimeField, JSONField, IntegerField
|
||||
TextChoices, TextField, BooleanField, CharField, DateTimeField, JSONField, IntegerField, AutoField
|
||||
)
|
||||
|
||||
from rsconcept.backend.apps.rsform.models.api_RSForm import RSForm, LibraryItem, LibraryItemType
|
||||
import rsconcept.backend.apps.rsform.messages as messages
|
||||
|
||||
|
||||
class GraphStatus(TextChoices):
|
||||
DRAFT = 'Draft',
|
||||
IN_PROGRESS = 'In progress',
|
||||
COMPLETED = 'Completed',
|
||||
FAILED = 'Failed'
|
||||
|
||||
|
||||
class OperationStatus(TextChoices):
|
||||
DRAFT = 'Draft',
|
||||
# IN_PROGRESS = 'In progress',
|
||||
COMPLETED = 'Completed',
|
||||
WARNING = 'Warning',
|
||||
FAILED = 'Failed'
|
||||
|
||||
|
||||
class SynthesisNodeType:
|
||||
LIBRARY = 'Library',
|
||||
SYNTHESIZED = 'Synthesized'
|
||||
class GraphStatus(TextChoices):
|
||||
DRAFT = 'Draft',
|
||||
COMPLETED = 'Completed',
|
||||
WARNING = 'Warning',
|
||||
FAILED = 'Failed'
|
||||
|
||||
|
||||
class ConceptOperation(Model):
|
||||
class SynthesisGraph(Model):
|
||||
status: CharField = CharField(
|
||||
verbose_name='Статус операции слияния',
|
||||
max_length=20,
|
||||
choices=GraphStatus
|
||||
)
|
||||
|
||||
|
||||
class InputNode(Model):
|
||||
graph_id: ForeignKey = ForeignKey(
|
||||
verbose_name='Схема синтеза',
|
||||
to=SynthesisGraph,
|
||||
on_delete=CASCADE
|
||||
)
|
||||
|
||||
vertical_coordinate: IntegerField = IntegerField(
|
||||
verbose_name='Вертикальная координата звена',
|
||||
)
|
||||
|
||||
horizontal_coordinate: IntegerField = IntegerField(
|
||||
verbose_name='Горизонтальная координата звена',
|
||||
)
|
||||
|
||||
rsform_id: IntegerField = IntegerField(
|
||||
verbose_name='Схема',
|
||||
null=True
|
||||
)
|
||||
|
||||
|
||||
class OperationNode(InputNode):
|
||||
name: CharField = CharField(
|
||||
verbose_name='Название',
|
||||
max_length=20
|
||||
)
|
||||
|
||||
node_type: CharField = CharField(
|
||||
verbose_name='Тип звена операции слияния',
|
||||
max_length=20,
|
||||
choices=SynthesisNodeType
|
||||
)
|
||||
|
||||
status: CharField(
|
||||
status: CharField = CharField(
|
||||
verbose_name='Статус операции слияния',
|
||||
max_length=20,
|
||||
choices=OperationStatus
|
||||
)
|
||||
|
||||
vertical_coordinate = IntegerField(
|
||||
verbose_name='Вертикальная координата звена',
|
||||
left_parent: ForeignKey = ForeignKey(
|
||||
verbose_name='Левый предок',
|
||||
to='rsform.LibraryItem',
|
||||
related_name='rsform_library_item_left',
|
||||
on_delete=SET_NULL,
|
||||
null=True
|
||||
)
|
||||
|
||||
horizontal_coordinate = IntegerField(
|
||||
verbose_name='Горизонтальная координата звена',
|
||||
right_parent: ForeignKey = ForeignKey(
|
||||
verbose_name='Правый предок',
|
||||
to='rsform.LibraryItem',
|
||||
related_name='rsform_library_item_right',
|
||||
on_delete=SET_NULL,
|
||||
null=True
|
||||
)
|
||||
|
||||
rsform = ForeignKey(
|
||||
verbose_name='Схема',
|
||||
to='rsform.LibraryItem'
|
||||
|
||||
class SynthesisSubstitution(Model):
|
||||
graph_id: ForeignKey = ForeignKey(
|
||||
verbose_name='Схема синтеза',
|
||||
to=SynthesisGraph,
|
||||
on_delete=CASCADE
|
||||
)
|
||||
|
||||
operation_type = CharField()
|
||||
|
||||
|
||||
class SynthesisGraph(Model):
|
||||
name: CharField = CharField(
|
||||
verbose_name='Название',
|
||||
max_length=20
|
||||
operation_id: ForeignKey = ForeignKey(
|
||||
verbose_name='Операция синтеза',
|
||||
to=OperationNode,
|
||||
on_delete=CASCADE
|
||||
)
|
||||
status: CharField = CharField(
|
||||
verbose_name='Статус схемы слияния',
|
||||
max_length=20,
|
||||
choices=GraphStatus,
|
||||
|
||||
leftCst: ForeignKey = ForeignKey(
|
||||
verbose_name='Конституента',
|
||||
to='Constituenta',
|
||||
related_name='constituenta_original',
|
||||
on_delete=SET_NULL,
|
||||
null=True
|
||||
)
|
||||
|
||||
rightCst: ForeignKey = ForeignKey(
|
||||
verbose_name='Подстановка',
|
||||
to='Constituenta',
|
||||
related_name='constituenta_substitution',
|
||||
on_delete=SET_NULL,
|
||||
null=True
|
||||
)
|
||||
|
||||
deleteRight: BooleanField = BooleanField(
|
||||
verbose_name='Удалить правую'
|
||||
)
|
||||
|
||||
takeLeftTerm: BooleanField = BooleanField(
|
||||
verbose_name='Использовать термин левой'
|
||||
)
|
||||
|
||||
|
||||
class SynthesisEdge(Model):
|
||||
synthesis_graph: ForeignKey(
|
||||
decoded_id: CharField = CharField(
|
||||
verbose_name='Id ребра на фронте',
|
||||
max_length=30,
|
||||
)
|
||||
|
||||
source_handle: CharField = CharField(
|
||||
verbose_name='',
|
||||
max_length=30,
|
||||
)
|
||||
graph_id: ForeignKey = ForeignKey(
|
||||
verbose_name='Схема синтеза',
|
||||
to=SynthesisGraph,
|
||||
on_delete=CASCADE
|
||||
)
|
||||
|
||||
node_from: ForeignKey(
|
||||
node_from: IntegerField = IntegerField(
|
||||
verbose_name='Звено-предок',
|
||||
to='rsform.LibraryItem'
|
||||
)
|
||||
|
||||
node_to: ForeignKey(
|
||||
node_to: IntegerField = IntegerField(
|
||||
verbose_name='Звено-наследник',
|
||||
to='rsform.LibraryItem'
|
||||
)
|
||||
|
|
|
@ -1,18 +1,112 @@
|
|||
from rest_framework import serializers
|
||||
from .data_access import InlineSynthesisSerializer
|
||||
from .data_access import CstSubstituteSerializerBase
|
||||
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
||||
|
||||
class SynthesisEdgeSerializer(serializers.Serializer):
|
||||
schema_from = serializers.IntegerField()
|
||||
schema_to = serializers.IntegerField()
|
||||
from ..models import Constituenta, LibraryItem
|
||||
from ..models.Synthesis import SynthesisGraph, SynthesisEdge, InputNode, OperationNode, SynthesisSubstitution
|
||||
|
||||
|
||||
class SynthesisNodeSerializer(serializers.Serializer):
|
||||
pk = serializers.IntegerField()
|
||||
vertical_coordinate = serializers.IntegerField()
|
||||
horizontal_coordinate = serializers.IntegerField()
|
||||
class SynthesisGraphSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SynthesisGraph
|
||||
fields = '__all__'
|
||||
|
||||
def create(self, validated_data):
|
||||
graph, created = SynthesisGraph.objects.update_or_create(
|
||||
id=validated_data['id'],
|
||||
defaults={'status': validated_data['status']}
|
||||
)
|
||||
return graph
|
||||
|
||||
|
||||
class SynthesisGraphSerializer(serializers.Serializer):
|
||||
pk = serializers.IntegerField()
|
||||
user = serializers.CharField()
|
||||
edges_list = serializers.ListField(child=SynthesisEdgeSerializer())
|
||||
class InputNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = InputNode
|
||||
fields = '__all__'
|
||||
|
||||
def create(self, validated_data_list):
|
||||
for validated_data in validated_data_list:
|
||||
input_node, created = InputNode.objects.update_or_create(
|
||||
id=validated_data['id'] if validated_data.get('id') else None,
|
||||
defaults={
|
||||
'graph_id': validated_data['graph_id'],
|
||||
'vertical_coordinate': validated_data['vertical_coordinate'],
|
||||
'horizontal_coordinate': validated_data['horizontal_coordinate'],
|
||||
'rsform_id': validated_data['rsform_id'],
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
class OperationNodeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = OperationNode
|
||||
fields = '__all__'
|
||||
|
||||
def create(self, validated_data_list):
|
||||
operations = []
|
||||
for validated_data in validated_data_list:
|
||||
operation_node, created = OperationNode.objects.update_or_create(
|
||||
id=validated_data['id'],
|
||||
defaults={
|
||||
'graph_id': validated_data['graph_id'],
|
||||
'vertical_coordinate': validated_data['vertical_coordinate'],
|
||||
'horizontal_coordinate': validated_data['horizontal_coordinate'],
|
||||
'rsform_id': validated_data['rsform_id'],
|
||||
'left_parent': validated_data.get('left_parent'),
|
||||
'right_parent': validated_data.get('right_parent'),
|
||||
}
|
||||
)
|
||||
operations.append(operation_node)
|
||||
return operations
|
||||
|
||||
|
||||
class SynthesisSubstitutionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SynthesisSubstitution
|
||||
fields = '__all__'
|
||||
|
||||
def create(self, validated_data_list):
|
||||
substitutions = []
|
||||
for validated_data in validated_data_list:
|
||||
substitution, created = SynthesisSubstitution.objects.update_or_create(
|
||||
id=validated_data['id'],
|
||||
defaults={
|
||||
'operation_id': validated_data['operation_id'],
|
||||
'graph_id': validated_data['graph_id'],
|
||||
'leftCst': validated_data['leftCst'],
|
||||
'rightCst': validated_data['rightCst'],
|
||||
'deleteRight': validated_data['deleteRight'],
|
||||
'takeLeftTerm': validated_data['takeLeftTerm'],
|
||||
}
|
||||
)
|
||||
substitutions.append(substitution)
|
||||
return substitutions
|
||||
|
||||
|
||||
class SynthesisEdgeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SynthesisEdge
|
||||
fields = '__all__'
|
||||
|
||||
def create(self, validated_data_list):
|
||||
for validated_data in validated_data_list:
|
||||
substitution, created = SynthesisEdge.objects.update_or_create(
|
||||
id=validated_data['id'],
|
||||
defaults={
|
||||
'graph_id': validated_data['graph_id'],
|
||||
'decoded_id': validated_data['decoded_id'],
|
||||
'source_handle': validated_data['source_handle'],
|
||||
'node_from': validated_data['node_from'],
|
||||
'node_to': validated_data['node_to'],
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
class RunSingleSynthesis(serializers.Serializer):
|
||||
operationId = serializers.IntegerField()
|
||||
|
||||
|
||||
class RunSingleSynthesisResponse(serializers.Serializer):
|
||||
rsformId = serializers.IntegerField()
|
||||
|
|
|
@ -30,7 +30,9 @@ urlpatterns = [
|
|||
path('cctext/inflect', views.inflect),
|
||||
path('cctext/generate-lexeme', views.generate_lexeme),
|
||||
path('cctext/parse', views.parse_text),
|
||||
path('synthesis/single', views.run_synthesis_view),
|
||||
path('synthesis/graph', views.run_sythesis_graph_view),
|
||||
path('synthesis/run_single', views.run_synthesis_view),
|
||||
path('synthesis/run_all', views.run_sythesis_graph_view),
|
||||
path('synthesis/save', views.save_synthesis_graph),
|
||||
path('synthesis/<int:pk_item>', views.get_synthesis_graph),
|
||||
path('', include(library_router.urls)),
|
||||
]
|
||||
|
|
|
@ -9,5 +9,7 @@ from .versions import VersionViewset, create_version, export_file, retrieve_vers
|
|||
|
||||
from .synthesis import (
|
||||
run_synthesis_view,
|
||||
run_sythesis_graph_view
|
||||
run_sythesis_graph_view,
|
||||
save_synthesis_graph,
|
||||
get_synthesis_graph,
|
||||
)
|
||||
|
|
|
@ -5,72 +5,158 @@ from rest_framework import status
|
|||
from rest_framework.decorators import api_view
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from django.db.models import Q
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from ..models.Constituenta import Constituenta
|
||||
from ..models.LibraryItem import LibraryItem
|
||||
from ..models.api_RSForm import RSForm
|
||||
from ..models.Synthesis import SynthesisGraph, InputNode, OperationNode, SynthesisSubstitution, SynthesisEdge
|
||||
from ..serializers import RSFormSerializer, SynthesisGraphSerializer, InlineSynthesisSerializer
|
||||
from typing import cast
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from ..serializers.data_access import CstBaseSerializer, CstSerializer
|
||||
from ..serializers.synthesis import OperationNodeSerializer, InputNodeSerializer, \
|
||||
SynthesisSubstitutionSerializer, SynthesisEdgeSerializer, RunSingleSynthesis, RunSingleSynthesisResponse
|
||||
from ..utils import clone_rsform
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Run synthesis operation',
|
||||
summary='Get synthesis graph',
|
||||
tags=['Synthesis'],
|
||||
request=InlineSynthesisSerializer,
|
||||
auth=None
|
||||
)
|
||||
@api_view(['GET'])
|
||||
def get_synthesis_graph(request: Request, pk_item: int):
|
||||
input_nodes = InputNode.objects.filter(graph_id=pk_item)
|
||||
operation_nodes = OperationNode.objects.filter(graph_id=pk_item)
|
||||
edges = SynthesisEdge.objects.filter(graph_id=pk_item)
|
||||
substitutions = []
|
||||
for operation_node in operation_nodes:
|
||||
substitution_batch = SynthesisSubstitution.objects.filter(operation_id=operation_node.id)
|
||||
for substitution in substitution_batch:
|
||||
substitutions.append(substitution)
|
||||
|
||||
synthesis_graph = SynthesisGraphSerializer()
|
||||
synthesis_graph.create(validated_data={'id': pk_item, 'status': 'Draft'})
|
||||
input_nodes = InputNodeSerializer(instance=input_nodes, many=True)
|
||||
operation_nodes = (OperationNodeSerializer(instance=operation_nodes, many=True))
|
||||
edges = SynthesisEdgeSerializer(instance=edges, many=True)
|
||||
substitutions = SynthesisSubstitutionSerializer(instance=substitutions, many=True)
|
||||
for substitution in substitutions.data:
|
||||
substitution['leftCst'] = CstSerializer(instance=Constituenta.objects.get(id=substitution['leftCst'])).data
|
||||
substitution['rightCst'] = CstSerializer(instance=Constituenta.objects.get(id=substitution['rightCst'])).data
|
||||
return Response(data={
|
||||
'graph': synthesis_graph.data,
|
||||
'input_nodes': input_nodes.data,
|
||||
'operation_nodes': operation_nodes.data,
|
||||
'edges': edges.data,
|
||||
'substitutions': substitutions.data,
|
||||
})
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Save synthesis graph',
|
||||
tags=['Synthesis'],
|
||||
request=SynthesisGraphSerializer,
|
||||
responses={status.HTTP_200_OK: RSFormSerializer},
|
||||
auth=None
|
||||
)
|
||||
@api_view(['POST'])
|
||||
def run_synthesis_view(request: Request):
|
||||
serializer = InlineSynthesisSerializer(
|
||||
data=request.data,
|
||||
context={'user': request.user}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return run_synthesis(serializer=serializer)
|
||||
def save_synthesis_graph(request: Request):
|
||||
graph_data = request.data.get('graph')
|
||||
input_nodes_data = request.data.get('input_nodes')
|
||||
operation_nodes_data = request.data.get('operation_nodes')
|
||||
edges_data = request.data.get('edges')
|
||||
substitutions_data = request.data.get('substitutions')
|
||||
|
||||
synthesis_graph_serializer = SynthesisGraphSerializer()
|
||||
graph = synthesis_graph_serializer.create(validated_data=graph_data)
|
||||
|
||||
InputNode.objects.filter(graph_id=graph).delete()
|
||||
OperationNode.objects.filter(graph_id=graph).delete()
|
||||
SynthesisEdge.objects.filter(graph_id=graph).delete()
|
||||
SynthesisSubstitution.objects.filter(graph_id=graph).delete()
|
||||
|
||||
input_node_serializer = InputNodeSerializer()
|
||||
for input_node in input_nodes_data:
|
||||
input_node['graph_id'] = graph
|
||||
input_node_serializer.create(validated_data_list=input_nodes_data)
|
||||
|
||||
for operation_node in operation_nodes_data:
|
||||
operation_node['graph_id'] = graph
|
||||
operation_node['left_parent'] = LibraryItem.objects.get(id=operation_node['left_parent'])
|
||||
operation_node['right_parent'] = LibraryItem.objects.get(id=operation_node['right_parent'])
|
||||
operation_node_serializer = OperationNodeSerializer()
|
||||
operations = operation_node_serializer.create(validated_data_list=operation_nodes_data)
|
||||
|
||||
for edge in edges_data:
|
||||
edge['graph_id'] = graph
|
||||
|
||||
edge_serializer = SynthesisEdgeSerializer()
|
||||
edge_serializer.create(validated_data_list=edges_data)
|
||||
|
||||
operations_dict = {operation.id: operation for operation in operations}
|
||||
for substitution_data in substitutions_data:
|
||||
substitution_data['operation_id'] = operations_dict[substitution_data['operation_id']]
|
||||
substitution_data['rightCst'] = Constituenta.objects.get(id=substitution_data['rightCst']['id'])
|
||||
substitution_data['leftCst'] = Constituenta.objects.get(id=substitution_data['leftCst']['id'])
|
||||
substitution_data['graph_id'] = graph
|
||||
|
||||
substitution_serializer = SynthesisSubstitutionSerializer()
|
||||
substitutions = substitution_serializer.create(validated_data_list=substitutions_data)
|
||||
|
||||
return Response(synthesis_graph_serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary='Run synthesis graph',
|
||||
tags=['Synthesis'],
|
||||
request=InlineSynthesisSerializer,
|
||||
responses={status.HTTP_200_OK: RSFormSerializer},
|
||||
request=RunSingleSynthesis,
|
||||
responses={status.HTTP_200_OK: RunSingleSynthesisResponse},
|
||||
auth=None
|
||||
)
|
||||
@api_view(['POST'])
|
||||
def run_sythesis_graph_view(request: Request):
|
||||
serializer = SynthesisGraphSerializer(data=request.data)
|
||||
serializer = RunSingleSynthesis(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
for atomic_synthesis in serializer.validated_data:
|
||||
run_synthesis(atomic_synthesis)
|
||||
|
||||
|
||||
def run_synthesis(serializer: InlineSynthesisSerializer):
|
||||
left_schema = RSForm(serializer.validated_data['source'])
|
||||
right_schema = RSForm(serializer.validated_data['receiver'])
|
||||
constituents = cast(list[Constituenta], serializer.validated_data['items'])
|
||||
@extend_schema(
|
||||
summary='Run synthesis operation',
|
||||
tags=['Synthesis'],
|
||||
request=RunSingleSynthesis,
|
||||
responses={status.HTTP_200_OK: RunSingleSynthesisResponse},
|
||||
auth=None
|
||||
)
|
||||
@api_view(['POST'])
|
||||
def run_synthesis_view(request: Request):
|
||||
serializer = RunSingleSynthesis(
|
||||
data=request.data
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return run_synthesis(serializer=serializer)
|
||||
|
||||
|
||||
def run_synthesis(serializer: RunSingleSynthesis):
|
||||
operation_id = serializer.data['operationId']
|
||||
operation = OperationNode.objects.get(id=operation_id)
|
||||
|
||||
left_schema = RSForm(operation.left_parent)
|
||||
right_schema = RSForm(operation.right_parent)
|
||||
substitutions = SynthesisSubstitution.objects.filter(operation_id=operation_id)
|
||||
|
||||
left_schema_copy = clone_rsform(left_schema)
|
||||
copied_constituents = left_schema_copy.item.constituenta_set
|
||||
used_constiuents = set()
|
||||
right_constituents = right_schema.item.constituenta_set.filter()
|
||||
left_schema_copy.insert_copy(right_constituents)
|
||||
|
||||
for substitution in serializer.validated_data['substitutions']:
|
||||
original = cast(Constituenta, substitution['original'])
|
||||
replacement = cast(Constituenta, substitution['substitution'])
|
||||
if original in constituents:
|
||||
index = next(i for (i, cst) in enumerate(constituents) if cst == original)
|
||||
original = copied_constituents[index]
|
||||
else:
|
||||
index = next(i for (i, cst) in enumerate(constituents) if cst == replacement)
|
||||
replacement = copied_constituents[index]
|
||||
left_schema_copy.substitute(original, replacement, substitution['transfer_term'])
|
||||
if substitution['transfer_term']:
|
||||
used_constiuents.add(replacement.pk)
|
||||
unused_constitunents = {cst for cst in right_schema.item.constituenta_set() if cst.pk not in used_constiuents}
|
||||
left_schema_copy.insert_copy(list(unused_constitunents))
|
||||
for substitution in substitutions:
|
||||
original = cast(Constituenta, substitution.leftCst)
|
||||
replacement = cast(Constituenta, substitution.rightCst)
|
||||
left_schema_copy.substitute(original, replacement, (not substitution.deleteRight) and substitution.takeLeftTerm)
|
||||
|
||||
left_schema.restore_order()
|
||||
return Response(
|
||||
|
@ -78,7 +164,6 @@ def run_synthesis(serializer: InlineSynthesisSerializer):
|
|||
data=RSFormSerializer(left_schema_copy.item).data
|
||||
)
|
||||
|
||||
return
|
||||
right_rsform_copy = clone_rsform(right_schema)
|
||||
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
|
15
rsconcept/frontend/.vscode/launch.json
vendored
Normal file
15
rsconcept/frontend/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -17,7 +17,7 @@ import {
|
|||
LibraryItemType
|
||||
} from '@/models/library';
|
||||
import { ILibraryCreateData } from '@/models/library';
|
||||
import { IOperationSchemaData } from '@/models/oss';
|
||||
import { IOperationSchemaData, IRunSynthesis, IRunSynthesisResponse } from '@/models/oss';
|
||||
import {
|
||||
IConstituentaList,
|
||||
IConstituentaMeta,
|
||||
|
@ -51,7 +51,7 @@ import {
|
|||
IUserUpdatePassword
|
||||
} from '@/models/user';
|
||||
import { buildConstants } from '@/utils/buildConstants';
|
||||
import {ISynthesisData} from "@/models/synthesis.ts";
|
||||
import { ISynthesisGraphData } from '@/models/oss.ts';
|
||||
|
||||
const defaultOptions = {
|
||||
xsrfCookieName: 'csrftoken',
|
||||
|
@ -86,6 +86,7 @@ interface IFrontRequest<RequestData, ResponseData> {
|
|||
export interface FrontPush<DataType> extends IFrontRequest<DataType, undefined> {
|
||||
data: DataType;
|
||||
}
|
||||
|
||||
export interface FrontPull<DataType> extends IFrontRequest<undefined, DataType> {
|
||||
onSuccess: DataCallback<DataType>;
|
||||
}
|
||||
|
@ -95,7 +96,8 @@ export interface FrontExchange<RequestData, ResponseData> extends IFrontRequest<
|
|||
onSuccess: DataCallback<ResponseData>;
|
||||
}
|
||||
|
||||
export interface FrontAction extends IFrontRequest<undefined, undefined> {}
|
||||
export interface FrontAction extends IFrontRequest<undefined, undefined> {
|
||||
}
|
||||
|
||||
interface IAxiosRequest<RequestData, ResponseData> {
|
||||
endpoint: string;
|
||||
|
@ -235,26 +237,10 @@ export function postCloneLibraryItem(target: string, request: FrontExchange<IRSF
|
|||
}
|
||||
|
||||
export function getOssDetails(target: string, request: FrontPull<IOperationSchemaData>) {
|
||||
request.onSuccess({
|
||||
id: Number(target),
|
||||
comment: '123',
|
||||
alias: 'oss1',
|
||||
access_policy: AccessPolicy.PUBLIC,
|
||||
editors: [],
|
||||
owner: 1,
|
||||
item_type: LibraryItemType.OSS,
|
||||
location: '/U',
|
||||
read_only: false,
|
||||
subscribers: [],
|
||||
time_create: '0',
|
||||
time_update: '0',
|
||||
title: 'TestOss',
|
||||
visible: false
|
||||
AxiosGet({
|
||||
endpoint: `/api/synthesis/${target}`,
|
||||
request: request
|
||||
});
|
||||
// AxiosGet({
|
||||
// endpoint: `/api/oss/${target}`, // TODO: endpoint to access OSS
|
||||
// request: request
|
||||
// });
|
||||
}
|
||||
|
||||
export function getRSFormDetails(target: string, version: string, request: FrontPull<IRSFormData>) {
|
||||
|
@ -438,16 +424,24 @@ export function patchUploadTRS(target: string, request: FrontExchange<IRSFormUpl
|
|||
}
|
||||
});
|
||||
}
|
||||
export function patchInlineSynthesis(request: FrontExchange<IInlineSynthesisData, IRSFormData>) {
|
||||
|
||||
export function patchInlineSynthesis(request: FrontExchange<ISynthesisGraphData, ISynthesisGraphData>) {
|
||||
AxiosPatch({
|
||||
endpoint: `/api/operations/inline-synthesis`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function postSynthesis(request: FrontExchange<ISynthesisData, IRSFormData>){
|
||||
AxiosPatch({
|
||||
endpoint: `/api/synthesis/single`,
|
||||
export function runSingleSynthesis(request: FrontExchange<IRunSynthesis, IRunSynthesisResponse>) {
|
||||
AxiosPost({
|
||||
endpoint: `/api/synthesis/run_single`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function postSynthesisGraph(request: FrontExchange<ISynthesisGraphData, ISynthesisGraphData>) {
|
||||
AxiosPost({
|
||||
endpoint: `/api/synthesis/save`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
@ -528,10 +522,10 @@ function AxiosGet<ResponseData>({ endpoint, request, options }: IAxiosRequest<un
|
|||
}
|
||||
|
||||
function AxiosPost<RequestData, ResponseData>({
|
||||
endpoint,
|
||||
request,
|
||||
options
|
||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||
endpoint,
|
||||
request,
|
||||
options
|
||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||
if (request.setLoading) request.setLoading(true);
|
||||
axiosInstance
|
||||
.post<ResponseData>(endpoint, request.data, options)
|
||||
|
@ -547,10 +541,10 @@ function AxiosPost<RequestData, ResponseData>({
|
|||
}
|
||||
|
||||
function AxiosDelete<RequestData, ResponseData>({
|
||||
endpoint,
|
||||
request,
|
||||
options
|
||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||
endpoint,
|
||||
request,
|
||||
options
|
||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||
if (request.setLoading) request.setLoading(true);
|
||||
axiosInstance
|
||||
.delete<ResponseData>(endpoint, options)
|
||||
|
@ -566,10 +560,10 @@ function AxiosDelete<RequestData, ResponseData>({
|
|||
}
|
||||
|
||||
function AxiosPatch<RequestData, ResponseData>({
|
||||
endpoint,
|
||||
request,
|
||||
options
|
||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||
endpoint,
|
||||
request,
|
||||
options
|
||||
}: IAxiosRequest<RequestData, ResponseData>) {
|
||||
if (request.setLoading) request.setLoading(true);
|
||||
axiosInstance
|
||||
.patch<ResponseData>(endpoint, request.data, options)
|
||||
|
|
|
@ -1,24 +1,61 @@
|
|||
import {memo, type FC} from 'react';
|
||||
import {Handle, Position, type NodeProps} from '@reactflow/core';
|
||||
import { memo, type FC } from 'react';
|
||||
import { Handle, Position, type NodeProps } from '@reactflow/core';
|
||||
import Button from '@/components/ui/Button.tsx';
|
||||
import { PiPlugsConnected } from 'react-icons/pi';
|
||||
import { CiSquareRemove } from 'react-icons/ci';
|
||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||
import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx';
|
||||
|
||||
const InputNode: FC<NodeProps> = ({data, xPos, yPos}) => {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Bottom}/>
|
||||
<div>
|
||||
<div>
|
||||
Тип: <strong>{data.label}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Схема из библиотеки:{' '}
|
||||
<strong>
|
||||
RSForm
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
interface InputNodeProps {
|
||||
id: string;
|
||||
data: {
|
||||
label: string;
|
||||
onDelete: (nodeId: string) => void;
|
||||
};
|
||||
bound_rsform_id: number;
|
||||
}
|
||||
|
||||
</>
|
||||
);
|
||||
|
||||
const InputNode: FC<InputNodeProps> = ({ id, data,bound_rsform_id }) => {
|
||||
const controller = useSynthesis();
|
||||
const { label, onDelete } = data;
|
||||
|
||||
const handleDelete = () => {
|
||||
onDelete(id);
|
||||
};
|
||||
|
||||
const handleClick = () =>{
|
||||
controller.selectNode(id);
|
||||
controller.showSelectInput();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Bottom} />
|
||||
<div>
|
||||
<MiniButton className="float-right"
|
||||
icon={<CiSquareRemove className="icon-red" />}
|
||||
title="Удалить"
|
||||
onClick={handleDelete}
|
||||
color={'red'}
|
||||
/>
|
||||
<div>
|
||||
Тип: <strong>Ввод</strong>
|
||||
</div>
|
||||
<div>
|
||||
Схема:{controller.getBind(id) === undefined? '': controller.getBind(id)}
|
||||
<strong>
|
||||
<MiniButton className="float-right"
|
||||
icon={<PiPlugsConnected className="icon-green" />}
|
||||
title="Привязать схему"
|
||||
onClick={() => {handleClick()}}
|
||||
/>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InputNode);
|
||||
|
|
|
@ -1,42 +1,91 @@
|
|||
import {memo, type FC, type CSSProperties} from 'react';
|
||||
import {Handle, Position, type NodeProps} from '@reactflow/core';
|
||||
import { memo, type FC, type CSSProperties } from 'react';
|
||||
import { Handle, Position, type NodeProps } from '@reactflow/core';
|
||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||
import { IoGitNetworkSharp } from 'react-icons/io5';
|
||||
import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx';
|
||||
import { CiSquareRemove } from 'react-icons/ci';
|
||||
import { VscDebugStart } from "react-icons/vsc";
|
||||
|
||||
const sourceHandleStyleA: CSSProperties = {left: 50};
|
||||
const sourceHandleStyleA: CSSProperties = { left: 50 };
|
||||
const sourceHandleStyleB: CSSProperties = {
|
||||
right: 50,
|
||||
left: 'auto',
|
||||
right: 50,
|
||||
left: 'auto'
|
||||
};
|
||||
|
||||
const OperationNode: FC<NodeProps> = ({data, xPos, yPos}) => {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Bottom}/>
|
||||
<div>
|
||||
<div>
|
||||
Тип: <strong>{data.label}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Схема:{' '}
|
||||
<strong>
|
||||
{xPos.toFixed(2)},{yPos.toFixed(2)}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
interface OperationNodeProps {
|
||||
id: string;
|
||||
data: {
|
||||
label: string;
|
||||
onDelete: (nodeId: string) => void;
|
||||
};
|
||||
xPos: number,
|
||||
yPos: number,
|
||||
}
|
||||
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Top}
|
||||
id="a"
|
||||
style={sourceHandleStyleA}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Top}
|
||||
id="b"
|
||||
style={sourceHandleStyleB}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
const OperationNode: FC<OperationNodeProps> = ({ id, data, xPos, yPos }) => {
|
||||
const controller = useSynthesis();
|
||||
const { label, onDelete } = data;
|
||||
|
||||
const handleDelete = () => {
|
||||
onDelete(id);
|
||||
};
|
||||
|
||||
const handleSubstitution = () =>{
|
||||
controller.selectNode(id);
|
||||
controller.showSynthesis();
|
||||
}
|
||||
|
||||
const handleSynthesis = () => {
|
||||
controller.singleSynthesis(id)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Bottom} />
|
||||
<div>
|
||||
<MiniButton className="float-right"
|
||||
icon={<CiSquareRemove className="icon-red" />}
|
||||
title="Удалить"
|
||||
onClick={handleDelete}
|
||||
color={'red'}
|
||||
/>
|
||||
<div>
|
||||
Тип: <strong>Отождествление</strong>
|
||||
</div>
|
||||
<div>
|
||||
Схема:{' '}
|
||||
<strong>
|
||||
</strong>
|
||||
<MiniButton
|
||||
className="float-right"
|
||||
icon={<VscDebugStart className="icon-green" />}
|
||||
title="Синтез"
|
||||
onClick={() => handleSynthesis()}
|
||||
/>
|
||||
<MiniButton
|
||||
className="float-right"
|
||||
icon={<IoGitNetworkSharp className="icon-green" />}
|
||||
title="Отождествления"
|
||||
onClick={() => handleSubstitution()}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Top}
|
||||
id="a"
|
||||
style={sourceHandleStyleA}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Top}
|
||||
id="b"
|
||||
style={sourceHandleStyleB}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(OperationNode);
|
||||
|
|
|
@ -3,9 +3,16 @@
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
.react-flow__node-input {
|
||||
border: 1px solid #555;
|
||||
padding: 10px;
|
||||
width: 150px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.react-flow__node-custom {
|
||||
border: 1px solid #555;
|
||||
padding: 10px;
|
||||
width: 200px;
|
||||
width: 250px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import {useCallback} from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
type Connection,
|
||||
type Edge,
|
||||
type Node,
|
||||
ReactFlow,
|
||||
addEdge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
type Connection,
|
||||
type Edge,
|
||||
type Node, OnSelectionChangeParams
|
||||
} from '@reactflow/core';
|
||||
|
||||
|
||||
import OperationNode from './OperationNode';
|
||||
import InputNode from './InputNode';
|
||||
|
||||
|
@ -16,68 +17,38 @@ import InputNode from './InputNode';
|
|||
import '@reactflow/core/dist/style.css';
|
||||
|
||||
import './SynthesisFlow.css';
|
||||
import DlgSynthesis from "@/dialogs/DlgSynthesis.tsx";
|
||||
import { useState } from 'react';
|
||||
import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx';
|
||||
import { useConceptOptions } from '@/context/OptionsContext.tsx';
|
||||
|
||||
|
||||
const nodeTypes = {
|
||||
custom: OperationNode,
|
||||
input: InputNode,
|
||||
custom: OperationNode,
|
||||
input: InputNode
|
||||
};
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'input',
|
||||
data: {label: 'Node 1'},
|
||||
position: {x: 250, y: 5},
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
id: '2',
|
||||
data: {label: 'Node 2'},
|
||||
position: {x: 100, y: 100},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
data: {label: 'Node 3'},
|
||||
position: {x: 400, y: 100},
|
||||
type: 'custom',
|
||||
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
data: {label: 'Node 4'},
|
||||
position: {x: 400, y: 200},
|
||||
type: 'custom',
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
//{ id: 'e1-2', source: '1', target: '2', animated: true },
|
||||
//{ id: 'e1-3', source: '1', target: '3', animated: true },
|
||||
];
|
||||
|
||||
function Flow() {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const onConnect = useCallback(
|
||||
(params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
const controller = useSynthesis();
|
||||
const { calculateHeight, darkMode } = useConceptOptions();
|
||||
const canvasWidth = useMemo(() => {
|
||||
return 'calc(100vw - 1rem)';
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className="Flow" style={{height: 800, width: 1000}}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
onNodesChange={onNodesChange}
|
||||
edges={edges}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
fitView
|
||||
nodeTypes={nodeTypes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||
return (
|
||||
<div className="relative" style={{ height: canvasHeight, width: canvasWidth }}>
|
||||
<ReactFlow
|
||||
nodes={controller.getNodes()}
|
||||
onNodesChange={controller.onNodesChange}
|
||||
onNodesDelete={controller.onNodesDelete}
|
||||
edges={controller.getEdges()}
|
||||
onEdgesChange={controller.onEdgesChange}
|
||||
onConnect={controller.onConnect}
|
||||
fitView
|
||||
nodeTypes={nodeTypes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Flow;
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ConstituentaID, IRSForm, ISubstitution } from '@/models/rsform';
|
|||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import PickSubstitutions from '../../components/select/PickSubstitutions';
|
||||
import { ISynthesisSubstitution } from '@/models/oss.ts';
|
||||
|
||||
interface SubstitutionsTabProps {
|
||||
receiver?: IRSForm;
|
||||
|
@ -16,7 +17,7 @@ interface SubstitutionsTabProps {
|
|||
error?: ErrorData;
|
||||
|
||||
substitutions: ISubstitution[];
|
||||
setSubstitutions: React.Dispatch<React.SetStateAction<ISubstitution[]>>;
|
||||
setSubstitutions: React.Dispatch<React.SetStateAction<ISubstitution[] | ISynthesisSubstitution[]>>;
|
||||
}
|
||||
|
||||
function SubstitutionsTab({
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Checkbox from '@/components/ui/Checkbox.tsx';
|
||||
import FileInput from '@/components/ui/FileInput.tsx';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal.tsx';
|
||||
import { useRSForm } from '@/context/RSFormContext.tsx';
|
||||
import { IRSForm, IRSFormUploadData, ISubstitution } from '@/models/rsform.ts';
|
||||
import { EXTEOR_TRS_FILE } from '@/utils/constants.ts';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
import clsx from 'clsx';
|
||||
import TabLabel from '@/components/ui/TabLabel.tsx';
|
||||
import SubstitutionsTab from '@/dialogs/DlgInlineSynthesis/SubstitutionsTab.tsx';
|
||||
import useRSFormDetails from '@/hooks/useRSFormDetails.ts';
|
||||
import { LibraryItemID } from '@/models/library.ts';
|
||||
import { ISynthesisData } from '@/models/synthesis.ts';
|
||||
import { TabID } from '@/dialogs/DlgInlineSynthesis/DlgInlineSynthesis.tsx';
|
||||
import SynthesisSubstitutionsTab from '@/pages/OssPage/SynthesisSubstitutionsTab.tsx';
|
||||
import SchemaTab from '@/dialogs/DlgInlineSynthesis/SchemaTab.tsx';
|
||||
import {
|
||||
Node
|
||||
} from '@reactflow/core';
|
||||
import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx';
|
||||
|
||||
interface DlgCreateSynthesisProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export enum SynthesisTabID {
|
||||
SCHEMA = 0,
|
||||
SUBSTITUTIONS = 1
|
||||
}
|
||||
|
||||
function DlgSelectInputScheme({ nodeId, hideWindow }: DlgCreateSynthesisProps) {
|
||||
const controller = useSynthesis();
|
||||
|
||||
const [activeTab, setActiveTab] = useState(SynthesisTabID.SCHEMA);
|
||||
const [selected, setSelected] = useState<LibraryItemID[]>([]);
|
||||
const [substitutions, setSubstitutions] = useState<ISubstitution[]>([]);
|
||||
const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined);
|
||||
|
||||
|
||||
const schemaPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<SchemaTab selected={donorID} setSelected={setDonorID} />
|
||||
</TabPanel>
|
||||
),
|
||||
[donorID]
|
||||
);
|
||||
|
||||
function handleSubmit() {
|
||||
if (donorID !== undefined) {
|
||||
controller.updateBounds(nodeId, donorID);
|
||||
}
|
||||
}
|
||||
|
||||
function validate() {
|
||||
if (donorID === undefined) {
|
||||
toast.error('Выберите источник конституент');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header="Синтез концептуальных скем"
|
||||
hideWindow={hideWindow}
|
||||
submitText="Привязать"
|
||||
className="w-[25rem] px-6"
|
||||
canSubmit={validate()}
|
||||
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Tabs
|
||||
selectedTabClassName="clr-selected"
|
||||
className="flex flex-col"
|
||||
selectedIndex={activeTab}
|
||||
onSelect={setActiveTab}
|
||||
>
|
||||
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
|
||||
<TabLabel label="Схема" title="Источник конституент" className="w-[8rem]" />
|
||||
</TabList>
|
||||
{schemaPanel}
|
||||
</Tabs>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgSelectInputScheme;
|
120
rsconcept/frontend/src/dialogs/DlgOssGraph/DlgSynthesis.tsx
Normal file
120
rsconcept/frontend/src/dialogs/DlgOssGraph/DlgSynthesis.tsx
Normal file
|
@ -0,0 +1,120 @@
|
|||
'use client';
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal.tsx';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
import clsx from 'clsx';
|
||||
import TabLabel from '@/components/ui/TabLabel.tsx';
|
||||
import useRSFormDetails from '@/hooks/useRSFormDetails.ts';
|
||||
import SynthesisSubstitutionsTab from '@/pages/OssPage/SynthesisSubstitutionsTab.tsx';
|
||||
import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx';
|
||||
import { ISynthesisSubstitution } from '@/models/oss.ts';
|
||||
|
||||
interface DlgCreateSynthesisProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
nodeId: string;
|
||||
onSynthesis: (data: ISynthesisSubstitution[]) => void;
|
||||
}
|
||||
|
||||
export enum SynthesisTabID {
|
||||
SCHEMA = 0,
|
||||
SUBSTITUTIONS = 1
|
||||
}
|
||||
|
||||
function DlgSynthesis({ hideWindow, nodeId, onSynthesis }: DlgCreateSynthesisProps) {
|
||||
const controller = useSynthesis();
|
||||
|
||||
const [activeTab, setActiveTab] = useState(SynthesisTabID.SCHEMA);
|
||||
const sourceLeft = useRSFormDetails({
|
||||
target: controller.getNodeParentsRsform(nodeId)[0] ?
|
||||
String(controller.getNodeParentsRsform(nodeId)[0]) : undefined
|
||||
});
|
||||
const sourceRight = useRSFormDetails({
|
||||
target: controller.getNodeParentsRsform(nodeId)[1] ?
|
||||
String(controller.getNodeParentsRsform(nodeId)[1]) : undefined
|
||||
});
|
||||
|
||||
|
||||
//const validated = useMemo(() => !!source.schema && selected.length > 0, [source.schema, selected]);
|
||||
function handleSubmit() {
|
||||
const parents = controller.getNodeParentsRsform(nodeId);
|
||||
|
||||
if (parents.length != 2) {
|
||||
return;
|
||||
}
|
||||
const data: ISynthesisSubstitution[] = controller.substitutions.map((item) => ({
|
||||
id: null,
|
||||
operation_id: nodeId,
|
||||
leftCst: item.leftCst,
|
||||
rightCst: item.rightCst,
|
||||
deleteRight: item.deleteRight,
|
||||
takeLeftTerm: item.takeLeftTerm
|
||||
}));
|
||||
controller.setSubstitutions(data);
|
||||
}
|
||||
|
||||
function validated() {
|
||||
const parents = controller.getNodeParentsRsform(nodeId);
|
||||
return parents.length == 2;
|
||||
}
|
||||
|
||||
const schemaPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel></TabPanel>
|
||||
), []
|
||||
);
|
||||
|
||||
const selectedSubstitutions = useMemo(
|
||||
() => controller.getSubstitution(nodeId),
|
||||
[controller, nodeId]
|
||||
);
|
||||
|
||||
const setSelectedSubstitutions = (newElement: ISynthesisSubstitution[]) => {
|
||||
controller.updateSubstitution(nodeId, newElement, controller.setSubstitutions);
|
||||
};
|
||||
|
||||
|
||||
const substitutesPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<SynthesisSubstitutionsTab
|
||||
receiver={sourceLeft.schema}
|
||||
source={sourceRight.schema}
|
||||
substitutions={selectedSubstitutions}
|
||||
setSubstitutions={setSelectedSubstitutions}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[sourceLeft.schema, sourceRight.schema, controller]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header="Синтез концептуальных схем"
|
||||
hideWindow={hideWindow}
|
||||
submitText="Сохранить"
|
||||
className="w-[25rem] px-6"
|
||||
canSubmit={validated}
|
||||
|
||||
onSubmit={handleSubmit}
|
||||
|
||||
>
|
||||
<Tabs
|
||||
selectedTabClassName="clr-selected"
|
||||
className="flex flex-col"
|
||||
selectedIndex={activeTab}
|
||||
onSelect={setActiveTab}
|
||||
>
|
||||
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
|
||||
<TabLabel label="Схема" title="Источник конституент" className="w-[8rem]" />
|
||||
<TabLabel label="Отождествления" title="Таблица отождествлений" className="w-[8rem]" />
|
||||
</TabList>
|
||||
{schemaPanel}
|
||||
{substitutesPanel}
|
||||
</Tabs>
|
||||
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgSynthesis;
|
|
@ -1,113 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import {toast} from 'react-toastify';
|
||||
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import FileInput from '@/components/ui/FileInput';
|
||||
import Modal, {ModalProps} from '@/components/ui/Modal';
|
||||
import {useRSForm} from '@/context/RSFormContext';
|
||||
import {IRSForm, IRSFormUploadData, ISubstitution} from '@/models/rsform';
|
||||
import {EXTEOR_TRS_FILE} from '@/utils/constants';
|
||||
import {TabList, TabPanel, Tabs} from "react-tabs";
|
||||
import clsx from "clsx";
|
||||
import TabLabel from "@/components/ui/TabLabel.tsx";
|
||||
import SubstitutionsTab from "@/dialogs/DlgInlineSynthesis/SubstitutionsTab.tsx";
|
||||
import useRSFormDetails from "@/hooks/useRSFormDetails.ts";
|
||||
import {LibraryItemID} from "@/models/library.ts";
|
||||
import {ISynthesisData} from "@/models/synthesis.ts";
|
||||
import {TabID} from "@/dialogs/DlgInlineSynthesis/DlgInlineSynthesis.tsx";
|
||||
import SynthesisSubstitutionsTab from "@/pages/SynthesisPage/SynthesisSubstitutionsTab.tsx";
|
||||
|
||||
interface DlgCreateSynthesisProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
schemaLeftID: number;
|
||||
schemaRightID: number;
|
||||
onSynthesis: (data: ISynthesisData) => void;
|
||||
|
||||
}
|
||||
|
||||
export enum SynthesisTabID {
|
||||
SCHEMA = 0,
|
||||
SUBSTITUTIONS = 1
|
||||
}
|
||||
|
||||
function DlgSynthesis({hideWindow, schemaLeftID, schemaRightID, onSynthesis}: DlgCreateSynthesisProps) {
|
||||
const [activeTab, setActiveTab] = useState(SynthesisTabID.SCHEMA);
|
||||
const sourceLeft = useRSFormDetails({target: schemaLeftID ? String(schemaLeftID) : undefined});
|
||||
const sourceRight = useRSFormDetails({target: schemaRightID ? String(schemaRightID) : undefined});
|
||||
const [selected, setSelected] = useState<LibraryItemID[]>([]);
|
||||
const [substitutions, setSubstitutions] = useState<ISubstitution[]>([]);
|
||||
|
||||
//const validated = useMemo(() => !!source.schema && selected.length > 0, [source.schema, selected]);
|
||||
|
||||
function handleSubmit() {
|
||||
if (!sourceLeft.schema || !sourceRight.schema) {
|
||||
return;
|
||||
}
|
||||
const data: ISynthesisData = {
|
||||
sourceLeft: schemaLeftID,
|
||||
sourceRight: schemaRightID,
|
||||
result: 1,
|
||||
substitutions: substitutions.map(item => ({
|
||||
original: item.deleteRight ? item.rightCst.id : item.leftCst.id,
|
||||
substitution: item.deleteRight ? item.leftCst.id : item.rightCst.id,
|
||||
transfer_term: !item.deleteRight && item.takeLeftTerm
|
||||
}))
|
||||
};
|
||||
onSynthesis(data);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(sourceLeft.schema && sourceRight.schema ? sourceLeft.schema?.items.map(cst => cst.id) : []);
|
||||
}, [sourceLeft.schema, sourceRight.schema]);
|
||||
|
||||
const schemaPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel></TabPanel>
|
||||
), []
|
||||
)
|
||||
|
||||
const substitutesPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<SynthesisSubstitutionsTab
|
||||
receiver={sourceLeft.schema}
|
||||
source={sourceRight.schema}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[sourceLeft.schema, sourceRight.schema, sourceLeft.loading, sourceRight.loading, selected, substitutions]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header='Синтез концептуальных скем'
|
||||
hideWindow={hideWindow}
|
||||
submitText='Запуск'
|
||||
className='w-[25rem] px-6'
|
||||
//canSubmit={validated}
|
||||
|
||||
onSubmit={handleSubmit}
|
||||
|
||||
>
|
||||
<Tabs
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col'
|
||||
selectedIndex={activeTab}
|
||||
onSelect={setActiveTab}
|
||||
>
|
||||
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
|
||||
<TabLabel label='Схема' title='Источник конституент' className='w-[8rem]'/>
|
||||
<TabLabel label='Отождествления' title='Таблица отождествлений' className='w-[8rem]'/>
|
||||
</TabList>
|
||||
{schemaPanel}
|
||||
{substitutesPanel}
|
||||
</Tabs>
|
||||
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgSynthesis;
|
|
@ -6,6 +6,7 @@ import { getOssDetails } from '@/app/backendAPI';
|
|||
import { type ErrorData } from '@/components/info/InfoError';
|
||||
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
|
||||
import { OssLoader } from '@/models/OssLoader';
|
||||
import { AccessPolicy, LibraryItemType } from '@/models/library.ts';
|
||||
|
||||
function useOssDetails({ target }: { target?: string }) {
|
||||
const [schema, setInner] = useState<IOperationSchema | undefined>(undefined);
|
||||
|
@ -27,6 +28,24 @@ function useOssDetails({ target }: { target?: string }) {
|
|||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const staticData = {
|
||||
id: Number(target),
|
||||
comment: '123',
|
||||
alias: 'oss1',
|
||||
access_policy: AccessPolicy.PUBLIC,
|
||||
editors: [],
|
||||
owner: 1,
|
||||
item_type: LibraryItemType.OSS,
|
||||
location: '/U',
|
||||
read_only: false,
|
||||
subscribers: [],
|
||||
time_create: '0',
|
||||
time_update: '0',
|
||||
title: 'TestOss',
|
||||
visible: false
|
||||
};
|
||||
|
||||
getOssDetails(target, {
|
||||
showError: true,
|
||||
setLoading: setCustomLoading ?? setLoading,
|
||||
|
@ -35,7 +54,11 @@ function useOssDetails({ target }: { target?: string }) {
|
|||
setError(error);
|
||||
},
|
||||
onSuccess: schema => {
|
||||
setSchema(schema);
|
||||
const combinedData = {
|
||||
...staticData,
|
||||
...schema
|
||||
}
|
||||
setSchema(combinedData);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -11,13 +11,14 @@ import { IOperationSchema, IOperationSchemaData } from './oss';
|
|||
export class OssLoader {
|
||||
private schema: IOperationSchemaData;
|
||||
|
||||
|
||||
constructor(input: IOperationSchemaData) {
|
||||
this.schema = input;
|
||||
}
|
||||
|
||||
produceOSS(): IOperationSchema {
|
||||
const result = this.schema as IOperationSchema;
|
||||
result.producedData = [1, 2, 3]; // TODO: put data processing here
|
||||
//result.producedData = [1, 2, 3]; // TODO: put data processing here
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,59 @@
|
|||
|
||||
import { ILibraryItemData } from './library';
|
||||
import { UserID } from './user';
|
||||
import { IConstituenta, ISubstitution } from '@/models/rsform.ts';
|
||||
|
||||
/**
|
||||
* Represents backend data for Schema of Synthesis Operations.
|
||||
*/
|
||||
export interface IOperationSchemaData extends ILibraryItemData {
|
||||
additional_data?: number[];
|
||||
input_nodes: IInputNode[];
|
||||
operation_nodes: ISynthesisNode[];
|
||||
edges: ISynthesisEdge[];
|
||||
substitutions: ISynthesisSubstitution[];
|
||||
graph: ISynthesisGraph;
|
||||
}
|
||||
|
||||
interface ISynthesisGraph {
|
||||
id: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface IInputNode {
|
||||
id: number | null;
|
||||
graph_id: number;
|
||||
vertical_coordinate: number;
|
||||
horizontal_coordinate: number;
|
||||
rsform_id: number;
|
||||
}
|
||||
|
||||
interface ISynthesisNode extends IInputNode {
|
||||
id: number | null;
|
||||
name: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface ISynthesisEdge {
|
||||
id: number | null;
|
||||
decoded_id: string;
|
||||
source_handle: string;
|
||||
graph_id: number;
|
||||
node_from: string;
|
||||
node_to: string;
|
||||
}
|
||||
|
||||
export interface ISynthesisSubstitution extends ISubstitution {
|
||||
id: number | null;
|
||||
graph_id: number;
|
||||
operation_id: string;
|
||||
}
|
||||
|
||||
export interface IRunSynthesis {
|
||||
operationId: number;
|
||||
}
|
||||
|
||||
export interface IRunSynthesisResponse {
|
||||
rsformId: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,5 +66,5 @@ export interface IOperationSchema extends IOperationSchemaData {
|
|||
subscribers: UserID[];
|
||||
editors: UserID[];
|
||||
|
||||
producedData: number[]; // TODO: modify this to store calculated state on load
|
||||
//producedData: number[]; // TODO: modify this to store calculated state on load
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import {LibraryItemID} from "@/models/library.ts";
|
||||
import {ICstSubstitute} from "@/models/rsform.ts";
|
||||
|
||||
export interface ISynthesisData {
|
||||
result: LibraryItemID;
|
||||
sourceLeft: LibraryItemID;
|
||||
sourceRight: LibraryItemID;
|
||||
substitutions: ICstSubstitute[];
|
||||
}
|
|
@ -1,13 +1,22 @@
|
|||
'use client';
|
||||
|
||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import SynthesisToolbar from '@/pages/OssPage/SynthesisToolbar.tsx';
|
||||
import SynthesisFlow from '@/components/ui/Synthesis/SynthesisFlow.tsx';
|
||||
import { SynthesisState } from '@/pages/OssPage/SynthesisContext.tsx';
|
||||
import { ReactFlowProvider } from '@reactflow/core';
|
||||
|
||||
function EditorOssGraph() {
|
||||
// TODO: Implement OSS editing UI here
|
||||
|
||||
return (
|
||||
<AnimateFade>
|
||||
<div className='py-3'>Реализация графического интерфейса</div>
|
||||
<ReactFlowProvider>
|
||||
<SynthesisState synthesisSchemaID="1">
|
||||
<SynthesisToolbar />
|
||||
<SynthesisFlow />
|
||||
</SynthesisState>
|
||||
</ReactFlowProvider>
|
||||
</AnimateFade>
|
||||
);
|
||||
}
|
||||
|
|
345
rsconcept/frontend/src/pages/OssPage/SynthesisContext.tsx
Normal file
345
rsconcept/frontend/src/pages/OssPage/SynthesisContext.tsx
Normal file
|
@ -0,0 +1,345 @@
|
|||
import { IRSFormData, ISubstitution } from '@/models/rsform.ts';
|
||||
import { DataCallback, runSingleSynthesis, postSynthesisGraph } from '@/app/backendAPI.ts';
|
||||
import { ISynthesisData } from '@/models/synthesis.ts';
|
||||
import { createContext, Dispatch, SetStateAction, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import DlgSynthesis from '@/dialogs/DlgOssGraph/DlgSynthesis.tsx';
|
||||
import {
|
||||
Node,
|
||||
Edge,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
type Connection,
|
||||
addEdge,
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
getConnectedEdges
|
||||
} from '@reactflow/core';
|
||||
import DlgSelectInputScheme from '@/dialogs/DlgOssGraph/DlgSelectInputScheme.tsx';
|
||||
import { IOperationSchemaData, IRunSynthesis, ISynthesisSubstitution } from '@/models/oss.ts';
|
||||
import { useOSS } from '@/context/OssContext.tsx';
|
||||
import viewConstituents from '@/pages/RSFormPage/ViewConstituents';
|
||||
|
||||
interface ISynthesisContext {
|
||||
synthesisSchemaID: string;
|
||||
singleSynthesis: (nodeId: number) => void;
|
||||
showSynthesis: () => void;
|
||||
showSelectInput: () => void;
|
||||
selectNode: (nodeId: string) => void;
|
||||
getSelectedNode: () => string | undefined;
|
||||
|
||||
addLibrarySchema: () => void;
|
||||
addSynthesisOperation: () => void;
|
||||
removeItem: () => void;
|
||||
runSynthesisLayer: () => void;
|
||||
|
||||
getNodes: () => Node[],
|
||||
getEdges: () => Edge[]
|
||||
|
||||
setNodes: (nodes: Node[]) => void;
|
||||
setEdges: (nodes: Edge[]) => void;
|
||||
|
||||
onNodesChange: any,
|
||||
onEdgesChange: any,
|
||||
onNodesDelete: any,
|
||||
onConnect: any,
|
||||
addBind: () => void,
|
||||
|
||||
updateBounds: (nodeId: string, newRsform: number) => void,
|
||||
getBind: (nodeId: string) => number
|
||||
getNodeParentsRsform: (nodeId: string) => number[]
|
||||
saveGraph: () => void;
|
||||
substitutions: ISynthesisSubstitution[]
|
||||
setSubstitutions: () => void,
|
||||
getSubstitution: (id: string) => ISynthesisSubstitution[],
|
||||
updateSubstitution: (id: string, substitution: ISynthesisSubstitution[], setSubstitutions: React.Dispatch<React.SetStateAction<ISynthesisSubstitution[]>>) => void,
|
||||
|
||||
}
|
||||
|
||||
interface IBoundMap {
|
||||
nodeId: string,
|
||||
rsformId: number
|
||||
}
|
||||
|
||||
const SynthesisContext = createContext<ISynthesisContext | null>(null);
|
||||
|
||||
interface SynthesisStateProps {
|
||||
synthesisSchemaID: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const useSynthesis = () => {
|
||||
const context = useContext(SynthesisContext);
|
||||
if (context === null) {
|
||||
throw new Error('useSynthesis has to be used within <SynthesisState.Provider>');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const SynthesisState = ({ synthesisSchemaID, children }: SynthesisStateProps) => {
|
||||
const [showSynthesisModal, setShowSynthesisModal] = useState(false);
|
||||
const [showSelectInputModal, setShowSelectInputModal] = useState(false);
|
||||
const [selectedNode, setSelectedNode] = useState<Node | null>(null);
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const [bounds, setBounds] = useState<IBoundMap[]>([]);
|
||||
const [substitutionBounds, setSubstitutionBounds] = useState<IBoundMap[]>([]);
|
||||
const [substitutions, setSubstitutions] = useState<ISynthesisSubstitution[]>([]);
|
||||
const { schema, loading, errorLoading } = useOSS();
|
||||
const ossSchema = useOSS();
|
||||
|
||||
|
||||
const getSubstitution = (operation_id: string): ISynthesisSubstitution[] => {
|
||||
return substitutions.filter(substitution => substitution.operation_id === operation_id);
|
||||
};
|
||||
|
||||
const updateSubstitution = (
|
||||
operation_id: number,
|
||||
newElements: ISubstitution[],
|
||||
setSubstitutions: React.Dispatch<React.SetStateAction<ISynthesisSubstitution[]>>
|
||||
) => {
|
||||
if (!Array.isArray(newElements)) {
|
||||
console.error('newElements should be an array.');
|
||||
return;
|
||||
}
|
||||
|
||||
setSubstitutions((prevSubstitutions) => {
|
||||
// Обновление существующих элементов
|
||||
const updatedSubstitutions = prevSubstitutions.map((substitution) => {
|
||||
const newElement = newElements.find((el) => el.id === substitution.id);
|
||||
if (newElement) {
|
||||
// Обновляем только соответствующие элементы
|
||||
return { ...substitution, ...newElement, operation_id: substitution.operation_id };
|
||||
}
|
||||
return substitution;
|
||||
});
|
||||
|
||||
// Добавление новых элементов с присвоением operation_id
|
||||
const newSubstitutions = newElements
|
||||
.filter((newElement) => !prevSubstitutions.some((sub) => sub.id === newElement.id))
|
||||
.map((newElement) => ({ ...newElement, operation_id }));
|
||||
|
||||
return [...updatedSubstitutions, ...newSubstitutions];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const extractEdgeId = (edgeId: string) => {
|
||||
const matches = edgeId.match(/\d+/g);
|
||||
const combined = matches ? matches.join('') : '';
|
||||
return Number(combined);
|
||||
};
|
||||
|
||||
|
||||
function saveGraph() {
|
||||
const data: IOperationSchemaData = {
|
||||
graph: {
|
||||
id: schema?.id,
|
||||
status: 'Draft'
|
||||
},
|
||||
input_nodes: nodes.filter((node) => node.type == 'input').map(item =>
|
||||
({
|
||||
id: item.id,
|
||||
vertical_coordinate: item.position.y,
|
||||
horizontal_coordinate: item.position.x,
|
||||
rsform_id: getBind(item.id)
|
||||
})),
|
||||
operation_nodes: nodes.filter((node) => node.type == 'custom').map(item =>
|
||||
({
|
||||
id: item.id,
|
||||
vertical_coordinate: item.position.y,
|
||||
horizontal_coordinate: item.position.x,
|
||||
left_parent: getNodeParentsRsform(item.id)[0],
|
||||
right_parent: getNodeParentsRsform(item.id)[1],
|
||||
rsform_id: getBind(item.id),
|
||||
name: 'name',
|
||||
status: 'status'
|
||||
})
|
||||
),
|
||||
edges: edges.map(item => ({
|
||||
id: extractEdgeId(item.id),
|
||||
decoded_id: item.id,
|
||||
source_handle: item.sourceHandle,
|
||||
node_from: item.source,
|
||||
node_to: item.target
|
||||
})
|
||||
),
|
||||
substitutions: substitutions
|
||||
};
|
||||
|
||||
postSynthesisGraph({
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (ossSchema.schema !== undefined) {
|
||||
const initialNodes = [
|
||||
...ossSchema.schema.input_nodes.map((node) => ({
|
||||
id: node.id?.toString() || 'null',
|
||||
data: { label: '123' },
|
||||
position: { x: node.horizontal_coordinate, y: node.vertical_coordinate },
|
||||
type: 'input'
|
||||
})),
|
||||
...ossSchema.schema.operation_nodes.map((node) => ({
|
||||
id: node.id?.toString() || 'null',
|
||||
data: { label: '123' },
|
||||
position: { x: node.horizontal_coordinate, y: node.vertical_coordinate },
|
||||
type: 'custom'
|
||||
}))
|
||||
];
|
||||
|
||||
const initialEdges = ossSchema.schema.edges.map((edge) => ({
|
||||
id: edge.decoded_id,
|
||||
source: String(edge.node_from),
|
||||
sourceHandle: edge.source_handle,
|
||||
target: String(edge.node_to)
|
||||
}));
|
||||
//const initialEdges = [{ id: 'reactflow__edge-2a-0', source: '2', target: '0' }, { id: 'reactflow__edge-2b-1', sourceHandle: 'b',source: '2', target: '1' }];
|
||||
|
||||
|
||||
setNodes(initialNodes);
|
||||
setEdges(initialEdges);
|
||||
setSubstitutions(ossSchema.schema.substitutions);
|
||||
[...ossSchema.schema.input_nodes, ...ossSchema.schema.operation_nodes].forEach((node) => {
|
||||
const nodeId = node.id?.toString() || 'null';
|
||||
const rsformId = node.rsform_id; // Предполагаем, что rsform_id есть в данных нод
|
||||
updateBounds(nodeId, rsformId);
|
||||
});
|
||||
}
|
||||
|
||||
}, [ossSchema]);
|
||||
|
||||
const getBind = (nodeId: string) => {
|
||||
const bound = bounds.find((item) => item.nodeId == nodeId);
|
||||
return bound ? bound.rsformId : null;
|
||||
|
||||
};
|
||||
const getBounds = (nodeId: string[]) => {
|
||||
const parentBounds = bounds.filter((item) => item.nodeId in nodeId);
|
||||
return parentBounds.map((item) => item.rsformId);
|
||||
};
|
||||
|
||||
function getNodeParentsRsform(nodeId: string) {
|
||||
const parentEdges = edges.filter((edge) => edge.source === nodeId);
|
||||
const parentNodeIds = parentEdges.map((edge) => edge.target);
|
||||
return getBounds(parentNodeIds);
|
||||
}
|
||||
|
||||
const updateBounds = (nodeId: string, newRsform: number) => {
|
||||
setBounds((prevItems) => {
|
||||
const existingItem = prevItems.find((item) => item.nodeId === nodeId);
|
||||
|
||||
if (existingItem) {
|
||||
return prevItems.map((item) =>
|
||||
item.nodeId === nodeId ? { ...item, rsformId: newRsform } : item
|
||||
);
|
||||
} else {
|
||||
return [...prevItems, { nodeId: nodeId, rsformId: newRsform }];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onConnect = useCallback(
|
||||
(params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)),
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
const onNodeDelete = useCallback(
|
||||
(nodeId: string) => {
|
||||
setNodes((nodes) => nodes.filter((node) => node.id !== nodeId));
|
||||
setEdges((edges) =>
|
||||
edges.filter((edge) => edge.source !== nodeId && edge.target !== nodeId)
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const singleSynthesis = useCallback(
|
||||
(operationId: number) => {
|
||||
const data: IRunSynthesis = {
|
||||
operationId: Number(operationId)
|
||||
};
|
||||
runSingleSynthesis({ data: data });
|
||||
},
|
||||
|
||||
[]
|
||||
);
|
||||
const newLibrarySchema = {
|
||||
id: String(nodes.length > 0 ? 1 + Math.max(...nodes.map(item => Number(item.id))) : 0),
|
||||
type: 'input',
|
||||
position: { x: 250, y: 5 },
|
||||
data: {
|
||||
label: 'Node 1', onDelete: onNodeDelete
|
||||
}
|
||||
};
|
||||
|
||||
const newOperation = {
|
||||
id: String(nodes.length > 0 ? 1 + Math.max(...nodes.map(item => Number(item.id))) : 0),
|
||||
type: 'custom',
|
||||
position: { x: 350, y: 20 },
|
||||
data: {
|
||||
label: 'Node 1', onDelete: onNodeDelete
|
||||
}
|
||||
};
|
||||
|
||||
const selectNode = useCallback(
|
||||
(nodeId: string) => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === nodeId) {
|
||||
setSelectedNode(node);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [nodes]
|
||||
);
|
||||
return (
|
||||
<SynthesisContext.Provider value={{
|
||||
synthesisSchemaID: synthesisSchemaID,
|
||||
singleSynthesis: singleSynthesis,
|
||||
showSynthesis: () => setShowSynthesisModal(true),
|
||||
showSelectInput: () => setShowSelectInputModal(true),
|
||||
selectNode: (nodeId) => selectNode(nodeId),
|
||||
getSelectedNode: () => selectedNode?.id,
|
||||
addLibrarySchema: () => {
|
||||
setNodes([...nodes, newLibrarySchema]);
|
||||
},
|
||||
addSynthesisOperation: () => {
|
||||
setNodes([...nodes, newOperation]);
|
||||
},
|
||||
setNodes: (nodes: Node[]) => {
|
||||
setNodes(nodes);
|
||||
},
|
||||
setEdges: (edges: Edge[]) => {
|
||||
setEdges(edges);
|
||||
},
|
||||
getNodes: () => nodes,
|
||||
getEdges: () => edges,
|
||||
onNodesChange: onNodesChange,
|
||||
onEdgesChange: onEdgesChange,
|
||||
onConnect: onConnect,
|
||||
updateBounds: updateBounds,
|
||||
getNodeParentsRsform: getNodeParentsRsform,
|
||||
getBind: getBind,
|
||||
saveGraph: saveGraph,
|
||||
setSubstitutions: setSubstitutions,
|
||||
substitutions: substitutions,
|
||||
updateSubstitution: updateSubstitution,
|
||||
getSubstitution: getSubstitution
|
||||
|
||||
}}>
|
||||
{showSynthesisModal ? (<DlgSynthesis
|
||||
hideWindow={() => setShowSynthesisModal(false)}
|
||||
nodeId={selectedNode?.id}
|
||||
onSynthesis={() => singleSynthesis}
|
||||
/>) : null}
|
||||
{showSelectInputModal ? (<DlgSelectInputScheme
|
||||
nodeId={selectedNode?.id}
|
||||
hideWindow={() => setShowSelectInputModal(false)}
|
||||
/>) : null}
|
||||
{children}
|
||||
|
||||
</SynthesisContext.Provider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
'use client';
|
||||
|
||||
import { ErrorData } from '@/components/info/InfoError.tsx';
|
||||
import DataLoader from '@/components/wrap/DataLoader.tsx';
|
||||
import { IRSForm, ISubstitution } from '@/models/rsform.ts';
|
||||
import { prefixes } from '@/utils/constants.ts';
|
||||
import PickSubstitutions from '../../components/select/PickSubstitutions';
|
||||
|
||||
interface SynthesisSubstitutionsTabProps {
|
||||
receiver?: IRSForm;
|
||||
source?: IRSForm;
|
||||
|
||||
error?: ErrorData;
|
||||
|
||||
substitutions: ISubstitution[];
|
||||
setSubstitutions: React.Dispatch<React.SetStateAction<ISubstitution[]>>;
|
||||
}
|
||||
|
||||
function SynthesisSubstitutionsTab({
|
||||
source,
|
||||
receiver,
|
||||
error,
|
||||
substitutions,
|
||||
setSubstitutions
|
||||
}: SynthesisSubstitutionsTabProps) {
|
||||
return (
|
||||
<DataLoader id="dlg-substitutions-tab" className="cc-column" isLoading={false} error={error} hasNoData={!source}>
|
||||
<PickSubstitutions
|
||||
items={substitutions}
|
||||
setItems={setSubstitutions}
|
||||
rows={10}
|
||||
prefixID={prefixes.cst_inline_synth_substitutes}
|
||||
schema1={receiver}
|
||||
schema2={source}
|
||||
/>
|
||||
</DataLoader>
|
||||
);
|
||||
}
|
||||
|
||||
export default SynthesisSubstitutionsTab;
|
45
rsconcept/frontend/src/pages/OssPage/SynthesisToolbar.tsx
Normal file
45
rsconcept/frontend/src/pages/OssPage/SynthesisToolbar.tsx
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx';
|
||||
import Button from '@/components/ui/Button.tsx';
|
||||
import { IoMdAdd, IoMdRemove } from 'react-icons/io';
|
||||
import { MdCallMerge } from 'react-icons/md';
|
||||
import Overlay from '@/components/ui/Overlay.tsx';
|
||||
import { MdLibraryAdd } from 'react-icons/md';
|
||||
|
||||
import { VscRunAll, VscSave } from 'react-icons/vsc';
|
||||
|
||||
function SynthesisToolbar() {
|
||||
const controller = useSynthesis();
|
||||
|
||||
return (
|
||||
<Overlay position="top-1 right-1/2 translate-x-1/2" className="flex">
|
||||
<Button
|
||||
title="Добавить схему из библиотеки"
|
||||
icon={<MdLibraryAdd />}
|
||||
onClick={() => controller.addLibrarySchema()}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Добавить операцию слияния"
|
||||
icon={<MdCallMerge />}
|
||||
onClick={() => controller.addSynthesisOperation()}
|
||||
/>
|
||||
<Button
|
||||
title={'Удалить форму'}
|
||||
icon={<IoMdRemove />}
|
||||
onClick={() => controller.removeItem()}
|
||||
/>
|
||||
<Button
|
||||
icon={<VscRunAll />}
|
||||
title="Синтез"
|
||||
onClick={() =>{}}
|
||||
/>
|
||||
<Button
|
||||
icon={<VscSave />}
|
||||
title="Сохранить"
|
||||
onClick={() => controller.saveGraph()}
|
||||
/>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
|
||||
export default SynthesisToolbar;
|
|
@ -1,62 +0,0 @@
|
|||
import {IRSForm, IRSFormData} from "@/models/rsform.ts";
|
||||
import {DataCallback, postSynthesis} from "@/app/backendAPI.ts";
|
||||
import {ISynthesisData} from "@/models/synthesis.ts";
|
||||
import {createContext, useCallback, useContext, useState} from "react";
|
||||
import {useLibrary} from "@/context/LibraryContext.tsx";
|
||||
import {useAuth} from "@/context/AuthContext.tsx";
|
||||
import useRSFormDetails from "@/hooks/useRSFormDetails.ts";
|
||||
import DlgSynthesis from "@/dialogs/DlgSynthesis.tsx";
|
||||
|
||||
interface ISynthesisContext {
|
||||
synthesisSchemaID: string;
|
||||
singleSynthesis: (data: ISynthesisData, callback?: DataCallback<IRSFormData>) => void;
|
||||
showSynthesis: () => void;
|
||||
|
||||
}
|
||||
|
||||
const SynthesisContext = createContext<ISynthesisContext | null>(null);
|
||||
|
||||
interface SynthesisStateProps {
|
||||
synthesisSchemaID: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const useSynthesis = () => {
|
||||
const context = useContext(SynthesisContext);
|
||||
if (context === null) {
|
||||
throw new Error('useSynthesis has to be used within <SynthesisState.Provider>');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export const SynthesisState = ({synthesisSchemaID, children}: SynthesisStateProps) => {
|
||||
const [showSynthesisModal, setShowSynthesisModal] = useState(false)
|
||||
|
||||
const singleSynthesis = useCallback(
|
||||
(data: ISynthesisData, callback?: DataCallback<IRSFormData>) => {
|
||||
postSynthesis({
|
||||
data: data,
|
||||
onSuccess: newData => {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<SynthesisContext.Provider value={{
|
||||
synthesisSchemaID: synthesisSchemaID,
|
||||
singleSynthesis: singleSynthesis,
|
||||
showSynthesis: () => setShowSynthesisModal(true),
|
||||
}}>
|
||||
{showSynthesisModal ? (< DlgSynthesis
|
||||
hideWindow={() => setShowSynthesisModal(false)}
|
||||
schemaLeftID={55}
|
||||
schemaRightID={56}
|
||||
onSynthesis={() => singleSynthesis}/>) : null}
|
||||
{children}
|
||||
</SynthesisContext.Provider>
|
||||
)
|
||||
};
|
|
@ -11,31 +11,23 @@ import {BsDownload, BsUpload} from 'react-icons/bs';
|
|||
import {useCallback, useState} from 'react';
|
||||
//import PropTypes from "prop-types";
|
||||
//import {useConceptNavigation} from "@/context/NavigationContext.tsx";
|
||||
import DlgSynthesis from "@/dialogs/DlgSynthesis.tsx";
|
||||
import DlgSynthesis from "@/dialogs/DlgOssGraph/DlgSynthesis.tsx";
|
||||
import SynthesisFlow from "@/components/ui/Synthesis/SynthesisFlow.tsx";
|
||||
import {IRSFormData} from "@/models/rsform.ts";
|
||||
import {toast} from "react-toastify";
|
||||
import {useRSForm} from "@/context/RSFormContext.tsx";
|
||||
import {DataCallback, postSynthesis} from "@/app/backendAPI.ts";
|
||||
import {DataCallback, runSingleSynthesis} from "@/app/backendAPI.ts";
|
||||
import {ISynthesisData} from "@/models/synthesis.ts";
|
||||
import useRSFormDetails from "@/hooks/useRSFormDetails.ts";
|
||||
import {useLibrary} from "@/context/LibraryContext.tsx";
|
||||
import {SynthesisState, useSynthesis} from "@/pages/SynthesisPage/SynthesisContext.tsx";
|
||||
import SynthesisToolbar from "@/pages/SynthesisPage/SynthesisToolbar.tsx";
|
||||
import {SynthesisState, useSynthesis} from "@/pages/OssPage/SynthesisContext.tsx";
|
||||
import SynthesisToolbar from "@/pages/OssPage/SynthesisToolbar.tsx";
|
||||
import {useParams} from "react-router-dom";
|
||||
|
||||
export const SynthesisPage = () => {
|
||||
const params = useParams();
|
||||
|
||||
//const router = useConceptNavigation();
|
||||
//const [sampleData, setSampleData] = useState([[1, 2, 3], [4, 5, 6]])
|
||||
|
||||
return (
|
||||
<SynthesisState synthesisSchemaID="1">
|
||||
<SynthesisToolbar/>
|
||||
<SynthesisFlow/>
|
||||
</SynthesisState>
|
||||
)
|
||||
}
|
||||
|
||||
export default SynthesisPage;
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
'use client';
|
||||
|
||||
import { ErrorData } from '@/components/info/InfoError';
|
||||
import DataLoader from '@/components/wrap/DataLoader';
|
||||
import { ConstituentaID, IRSForm, ISubstitution } from '@/models/rsform';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import SubstitutionsPicker from '../../components/select/SubstitutionsPicker';
|
||||
|
||||
interface SynthesisSubstitutionsTabProps {
|
||||
receiver?: IRSForm;
|
||||
source?: IRSForm;
|
||||
|
||||
error?: ErrorData;
|
||||
|
||||
substitutions: ISubstitution[];
|
||||
setSubstitutions: React.Dispatch<React.SetStateAction<ISubstitution[]>>;
|
||||
}
|
||||
|
||||
function SynthesisSubstitutionsTab({
|
||||
source,
|
||||
receiver,
|
||||
error,
|
||||
|
||||
substitutions,
|
||||
setSubstitutions
|
||||
}: SynthesisSubstitutionsTabProps) {
|
||||
return (
|
||||
<DataLoader id='dlg-substitutions-tab' className='cc-column' isLoading={false} error={error} hasNoData={!source}>
|
||||
<SubstitutionsPicker
|
||||
items={substitutions}
|
||||
setItems={setSubstitutions}
|
||||
rows={10}
|
||||
prefixID={prefixes.cst_inline_synth_substitutes}
|
||||
schema1={receiver}
|
||||
schema2={source}
|
||||
/>
|
||||
</DataLoader>
|
||||
);
|
||||
}
|
||||
|
||||
export default SynthesisSubstitutionsTab;
|
|
@ -1,39 +0,0 @@
|
|||
import {useSynthesis} from "@/pages/SynthesisPage/SynthesisContext.tsx";
|
||||
import Button from "@/components/ui/Button.tsx";
|
||||
import {IoMdAdd, IoMdRemove} from "react-icons/io";
|
||||
import {MdCallMerge} from "react-icons/md";
|
||||
import {BsDownload, BsUpload} from "react-icons/bs";
|
||||
import Overlay from "@/components/ui/Overlay.tsx";
|
||||
|
||||
|
||||
function SynthesisToolbar() {
|
||||
const controller = useSynthesis()
|
||||
|
||||
return (
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||
<Button
|
||||
title='Добавить форму'
|
||||
icon={<IoMdAdd/>}
|
||||
/>
|
||||
<Button
|
||||
title={'Удалить форму'}
|
||||
icon={<IoMdRemove/>}
|
||||
/>
|
||||
<Button
|
||||
icon={<MdCallMerge/>}
|
||||
title='Синтез'
|
||||
onClick={() => controller.showSynthesis()}
|
||||
/>
|
||||
<Button
|
||||
icon={<BsDownload/>}
|
||||
title='Импорт формы'
|
||||
/>
|
||||
<Button
|
||||
title='Экспорт формы'
|
||||
icon={<BsUpload/>}
|
||||
/>
|
||||
</Overlay>
|
||||
)
|
||||
}
|
||||
|
||||
export default SynthesisToolbar;
|
Loading…
Reference in New Issue
Block a user