mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-14 04:40:36 +03:00
F: Implement backend for prompts
This commit is contained in:
parent
409bfb0d94
commit
df72b1f527
0
rsconcept/backend/apps/prompt/__init__.py
Normal file
0
rsconcept/backend/apps/prompt/__init__.py
Normal file
12
rsconcept/backend/apps/prompt/admin.py
Normal file
12
rsconcept/backend/apps/prompt/admin.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
''' Admin view: Prompts for AI helper. '''
|
||||
from django.contrib import admin
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
@admin.register(models.PromptTemplate)
|
||||
class PromptTemplateAdmin(admin.ModelAdmin):
|
||||
''' Admin model: PromptTemplate. '''
|
||||
list_display = ('id', 'label', 'owner', 'is_shared')
|
||||
list_filter = ('is_shared', 'owner')
|
||||
search_fields = ('label', 'description', 'text')
|
8
rsconcept/backend/apps/prompt/apps.py
Normal file
8
rsconcept/backend/apps/prompt/apps.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
''' Application: Prompts for AI helper. '''
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PromptConfig(AppConfig):
|
||||
''' Application config. '''
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.prompt'
|
28
rsconcept/backend/apps/prompt/migrations/0001_initial.py
Normal file
28
rsconcept/backend/apps/prompt/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 5.2.4 on 2025-07-13 13:11
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PromptTemplate',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_shared', models.BooleanField(default=False, verbose_name='Общий доступ')),
|
||||
('label', models.CharField(max_length=255, verbose_name='Название')),
|
||||
('description', models.TextField(blank=True, verbose_name='Описание')),
|
||||
('text', models.TextField(blank=True, verbose_name='Содержание')),
|
||||
('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='prompt_templates', to=settings.AUTH_USER_MODEL, verbose_name='Владелец')),
|
||||
],
|
||||
),
|
||||
]
|
46
rsconcept/backend/apps/prompt/models/PromptTemplate.py
Normal file
46
rsconcept/backend/apps/prompt/models/PromptTemplate.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
''' Model: PromptTemplate for AI prompt storage and sharing. '''
|
||||
|
||||
from django.db import models
|
||||
|
||||
from apps.users.models import User
|
||||
|
||||
|
||||
class PromptTemplate(models.Model):
|
||||
'''Represents an AI prompt template, which can be user-owned or shared globally.'''
|
||||
owner = models.ForeignKey(
|
||||
verbose_name='Владелец',
|
||||
to=User,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='prompt_templates'
|
||||
)
|
||||
is_shared = models.BooleanField(
|
||||
verbose_name='Общий доступ',
|
||||
default=False
|
||||
)
|
||||
label = models.CharField(
|
||||
verbose_name='Название',
|
||||
max_length=255
|
||||
)
|
||||
description = models.TextField(
|
||||
verbose_name='Описание',
|
||||
blank=True
|
||||
)
|
||||
text = models.TextField(
|
||||
verbose_name='Содержание',
|
||||
blank=True
|
||||
)
|
||||
|
||||
def can_set_shared(self, user: User) -> bool:
|
||||
'''Return True if the user can set is_shared=True (admin/staff only).'''
|
||||
return user.is_superuser or user.is_staff
|
||||
|
||||
def can_access(self, user: User) -> bool:
|
||||
'''Return True if the user can access this template (shared or owner).'''
|
||||
if self.is_shared:
|
||||
return True
|
||||
return self.owner == user
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f'{self.label}'
|
3
rsconcept/backend/apps/prompt/models/__init__.py
Normal file
3
rsconcept/backend/apps/prompt/models/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
''' Django: Models for AI Prompts. '''
|
||||
|
||||
from .PromptTemplate import PromptTemplate
|
2
rsconcept/backend/apps/prompt/serializers/__init__.py
Normal file
2
rsconcept/backend/apps/prompt/serializers/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
''' Serializers for persistent data manipulation (AI Prompts). '''
|
||||
from .data_access import PromptTemplateSerializer
|
39
rsconcept/backend/apps/prompt/serializers/data_access.py
Normal file
39
rsconcept/backend/apps/prompt/serializers/data_access.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
''' Serializers for prompt template data access. '''
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from shared import messages as msg
|
||||
|
||||
from ..models import PromptTemplate
|
||||
|
||||
|
||||
class PromptTemplateSerializer(serializers.ModelSerializer):
|
||||
'''Serializer for PromptTemplate, enforcing permissions and ownership logic.'''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = PromptTemplate
|
||||
fields = ['id', 'owner', 'is_shared', 'label', 'description', 'text']
|
||||
read_only_fields = ['id', 'owner']
|
||||
|
||||
def validate_label(self, value):
|
||||
user = self.context['request'].user
|
||||
if PromptTemplate.objects.filter(owner=user, label=value).exists():
|
||||
raise serializers.ValidationError(msg.promptLabelTaken(value))
|
||||
return value
|
||||
|
||||
def validate_is_shared(self, value):
|
||||
user = self.context['request'].user
|
||||
if value and not (user.is_superuser or user.is_staff):
|
||||
raise serializers.ValidationError(msg.promptSharedPermissionDenied())
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['owner'] = self.context['request'].user
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
user = self.context['request'].user
|
||||
if 'is_shared' in validated_data:
|
||||
if validated_data['is_shared'] and not (user.is_superuser or user.is_staff):
|
||||
raise serializers.ValidationError(msg.promptSharedPermissionDenied())
|
||||
return super().update(instance, validated_data)
|
2
rsconcept/backend/apps/prompt/tests/__init__.py
Normal file
2
rsconcept/backend/apps/prompt/tests/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
''' Tests. '''
|
||||
from .t_prompts import *
|
113
rsconcept/backend/apps/prompt/tests/t_prompts.py
Normal file
113
rsconcept/backend/apps/prompt/tests/t_prompts.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
''' Testing API: Prompts. '''
|
||||
from rest_framework import status
|
||||
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
|
||||
from ..models import PromptTemplate
|
||||
|
||||
|
||||
class TestPromptTemplateViewSet(EndpointTester):
|
||||
''' Testing PromptTemplate viewset. '''
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.admin = self.user2
|
||||
self.admin.is_superuser = True
|
||||
self.admin.save()
|
||||
|
||||
|
||||
@decl_endpoint('/api/prompts/', method='post')
|
||||
def test_create_prompt(self):
|
||||
data = {
|
||||
'label': 'Test',
|
||||
'description': 'desc',
|
||||
'text': 'prompt text',
|
||||
'is_shared': False
|
||||
}
|
||||
response = self.executeCreated(data=data)
|
||||
self.assertEqual(response.data['label'], 'Test')
|
||||
self.assertEqual(response.data['owner'], self.user.pk)
|
||||
|
||||
|
||||
@decl_endpoint('/api/prompts/', method='post')
|
||||
def test_create_shared_prompt_by_admin(self):
|
||||
self.client.force_authenticate(user=self.admin)
|
||||
data = {
|
||||
'label': 'Shared',
|
||||
'description': 'desc',
|
||||
'text': 'prompt text',
|
||||
'is_shared': True
|
||||
}
|
||||
response = self.executeCreated(data=data)
|
||||
self.assertTrue(response.data['is_shared'])
|
||||
|
||||
|
||||
@decl_endpoint('/api/prompts/', method='post')
|
||||
def test_create_shared_prompt_by_user_forbidden(self):
|
||||
data = {
|
||||
'label': 'Shared',
|
||||
'description': 'desc',
|
||||
'text': 'prompt text',
|
||||
'is_shared': True
|
||||
}
|
||||
response = self.executeBadData(data=data)
|
||||
self.assertIn('is_shared', response.data)
|
||||
|
||||
|
||||
@decl_endpoint('/api/prompts/{item}/', method='patch')
|
||||
def test_update_prompt_owner(self):
|
||||
prompt = PromptTemplate.objects.create(owner=self.user, label='ToUpdate', description='', text='t')
|
||||
response = self.executeOK(data={'label': 'Updated'}, item=prompt.id)
|
||||
self.assertEqual(response.data['label'], 'Updated')
|
||||
|
||||
|
||||
@decl_endpoint('/api/prompts/{item}/', method='patch')
|
||||
def test_update_prompt_not_owner_forbidden(self):
|
||||
prompt = PromptTemplate.objects.create(owner=self.admin, label='Other', description='', text='t')
|
||||
response = self.executeForbidden(data={'label': 'Updated'}, item=prompt.id)
|
||||
|
||||
|
||||
@decl_endpoint('/api/prompts/{item}/', method='delete')
|
||||
def test_delete_prompt_owner(self):
|
||||
prompt = PromptTemplate.objects.create(owner=self.user, label='ToDelete', description='', text='t')
|
||||
self.executeNoContent(item=prompt.id)
|
||||
|
||||
|
||||
@decl_endpoint('/api/prompts/{item}/', method='delete')
|
||||
def test_delete_prompt_not_owner_forbidden(self):
|
||||
prompt = PromptTemplate.objects.create(owner=self.admin, label='Other2', description='', text='t')
|
||||
self.executeForbidden(item=prompt.id)
|
||||
|
||||
|
||||
@decl_endpoint('/api/prompts/available/', method='get')
|
||||
def test_available_endpoint(self):
|
||||
PromptTemplate.objects.create(
|
||||
owner=self.user,
|
||||
label='Mine',
|
||||
description='',
|
||||
text='t'
|
||||
)
|
||||
PromptTemplate.objects.create(
|
||||
owner=self.admin,
|
||||
label='Shared',
|
||||
description='',
|
||||
text='t',
|
||||
is_shared=True
|
||||
)
|
||||
response = self.executeOK()
|
||||
labels = [item['label'] for item in response.data]
|
||||
self.assertIn('Mine', labels)
|
||||
self.assertIn('Shared', labels)
|
||||
|
||||
|
||||
@decl_endpoint('/api/prompts/{item}/', method='patch')
|
||||
def test_permissions_on_shared(self):
|
||||
prompt = PromptTemplate.objects.create(
|
||||
owner=self.admin,
|
||||
label='Shared',
|
||||
description='',
|
||||
text='t',
|
||||
is_shared=True
|
||||
)
|
||||
self.client.force_authenticate(user=self.user)
|
||||
response = self.executeForbidden(data={'label': 'Nope'}, item=prompt.id)
|
9
rsconcept/backend/apps/prompt/urls.py
Normal file
9
rsconcept/backend/apps/prompt/urls.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
''' Routing: Prompts for AI helper. '''
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .views import PromptTemplateViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register('prompts', PromptTemplateViewSet, 'prompt-template')
|
||||
|
||||
urlpatterns = router.urls
|
2
rsconcept/backend/apps/prompt/views/__init__.py
Normal file
2
rsconcept/backend/apps/prompt/views/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
''' REST API: Endpoint processors for AI Prompts. '''
|
||||
from .prompts import PromptTemplateViewSet
|
52
rsconcept/backend/apps/prompt/views/prompts.py
Normal file
52
rsconcept/backend/apps/prompt/views/prompts.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
''' Views: PromptTemplate endpoints for AI prompt management. '''
|
||||
|
||||
from django.db import models
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import permissions, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from ..models import PromptTemplate
|
||||
from ..serializers import PromptTemplateSerializer
|
||||
|
||||
|
||||
class IsOwnerOrAdmin(permissions.BasePermission):
|
||||
'''Permission: Only owner or admin can modify, anyone can view shared.'''
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return obj.is_shared or obj.owner == request.user
|
||||
return obj.owner == request.user or request.user.is_staff or request.user.is_superuser
|
||||
|
||||
|
||||
@extend_schema(tags=['Prompts'])
|
||||
class PromptTemplateViewSet(viewsets.ModelViewSet):
|
||||
'''ViewSet: CRUD and listing for PromptTemplate, with sharing logic.'''
|
||||
queryset = PromptTemplate.objects.all()
|
||||
serializer_class = PromptTemplateSerializer
|
||||
permission_classes = [permissions.IsAuthenticated, IsOwnerOrAdmin]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if self.action == 'available':
|
||||
return PromptTemplate.objects.none()
|
||||
return PromptTemplate.objects.filter(models.Q(owner=user) | models.Q(is_shared=True)).distinct()
|
||||
|
||||
def get_object(self):
|
||||
obj = PromptTemplate.objects.get(pk=self.kwargs['pk'])
|
||||
self.check_object_permissions(self.request, obj)
|
||||
return obj
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(owner=self.request.user)
|
||||
|
||||
@extend_schema(summary='List user-owned and shared prompt templates')
|
||||
@action(detail=False, methods=['get'], url_path='available')
|
||||
def available(self, request):
|
||||
'''Return user-owned and shared prompt templates.'''
|
||||
user = request.user
|
||||
owned = PromptTemplate.objects.filter(owner=user)
|
||||
shared = PromptTemplate.objects.filter(is_shared=True)
|
||||
templates = (owned | shared).distinct()
|
||||
serializer = self.get_serializer(templates, many=True)
|
||||
return Response(serializer.data)
|
|
@ -4,11 +4,9 @@ from django.contrib import admin
|
|||
from . import models
|
||||
|
||||
|
||||
@admin.register(models.Constituenta)
|
||||
class ConstituentaAdmin(admin.ModelAdmin):
|
||||
''' Admin model: Constituenta. '''
|
||||
ordering = ['schema', 'order']
|
||||
list_display = ['schema', 'order', 'alias', 'term_resolved', 'definition_resolved']
|
||||
search_fields = ['term_resolved', 'definition_resolved']
|
||||
|
||||
|
||||
admin.site.register(models.Constituenta, ConstituentaAdmin)
|
||||
|
|
|
@ -76,6 +76,7 @@ INSTALLED_APPS = [
|
|||
'apps.library',
|
||||
'apps.rsform',
|
||||
'apps.oss',
|
||||
'apps.prompt',
|
||||
|
||||
'drf_spectacular',
|
||||
'drf_spectacular_sidecar',
|
||||
|
|
|
@ -11,6 +11,7 @@ urlpatterns = [
|
|||
path('api/', include('apps.library.urls')),
|
||||
path('api/', include('apps.rsform.urls')),
|
||||
path('api/', include('apps.oss.urls')),
|
||||
path('api/', include('apps.prompt.urls')),
|
||||
path('users/', include('apps.users.urls')),
|
||||
path('schema', SpectacularAPIView.as_view(), name='schema'),
|
||||
path('redoc', SpectacularRedocView.as_view()),
|
||||
|
|
|
@ -144,3 +144,19 @@ def passwordsNotMatch():
|
|||
|
||||
def emailAlreadyTaken():
|
||||
return 'Пользователь с данным email уже существует'
|
||||
|
||||
|
||||
def promptLabelTaken(label: str):
|
||||
return f'Шаблон с меткой "{label}" уже существует у пользователя.'
|
||||
|
||||
|
||||
def promptNotOwner():
|
||||
return 'Вы не являетесь владельцем этого шаблона.'
|
||||
|
||||
|
||||
def promptSharedPermissionDenied():
|
||||
return 'Только администратор может сделать шаблон общедоступным.'
|
||||
|
||||
|
||||
def promptNotFound():
|
||||
return 'Шаблон не найден.'
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
export interface IPromptTemplate {
|
||||
id: number;
|
||||
owner: number | null;
|
||||
is_shared: boolean;
|
||||
label: string;
|
||||
description: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
// ========= SCHEMAS ========
|
||||
|
|
|
@ -13,12 +13,14 @@ const mockPrompts: IPromptTemplate[] = [
|
|||
id: 1,
|
||||
owner: null,
|
||||
label: 'Greeting',
|
||||
is_shared: true,
|
||||
description: 'A simple greeting prompt.',
|
||||
text: 'Hello, ${name}! How can I assist you today?'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
owner: null,
|
||||
is_shared: true,
|
||||
label: 'Summary',
|
||||
description: 'Summarize the following text.',
|
||||
text: 'Please summarize the following: ${text}'
|
||||
|
|
13
rsconcept/frontend/src/features/ai/labels.ts
Normal file
13
rsconcept/frontend/src/features/ai/labels.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { PromptVariableType } from './models/prompting';
|
||||
|
||||
const describePromptVariableRecord: Record<PromptVariableType, string> = {
|
||||
[PromptVariableType.BLOCK]: 'Текущий блок операционной схемы',
|
||||
[PromptVariableType.OSS]: 'Текущая операционная схема',
|
||||
[PromptVariableType.SCHEMA]: 'Текущая концептуальный схема',
|
||||
[PromptVariableType.CONSTITUENTA]: 'Текущая конституента'
|
||||
};
|
||||
|
||||
/** Retrieves description for {@link PromptVariableType}. */
|
||||
export function describePromptVariable(itemType: PromptVariableType): string {
|
||||
return describePromptVariableRecord[itemType] ?? `UNKNOWN VARIABLE TYPE: ${itemType}`;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import { extractPromptVariables } from './prompting-api';
|
||||
|
||||
describe('extractPromptVariables', () => {
|
||||
it('extracts a single variable', () => {
|
||||
expect(extractPromptVariables('Hello {{name}}!')).toEqual(['name']);
|
||||
});
|
||||
|
||||
it('extracts multiple variables', () => {
|
||||
expect(extractPromptVariables('Hi {{firstName}}, your ID is {{user.id}}.')).toEqual(['firstName', 'user.id']);
|
||||
});
|
||||
|
||||
it('extracts variables with hyphens and dots', () => {
|
||||
expect(extractPromptVariables('Welcome {{user-name}} and {{user.name}}!')).toEqual(['user-name', 'user.name']);
|
||||
});
|
||||
|
||||
it('returns empty array if no variables', () => {
|
||||
expect(extractPromptVariables('No variables here!')).toEqual([]);
|
||||
});
|
||||
|
||||
it('ignores invalid variable patterns', () => {
|
||||
expect(extractPromptVariables('Hello {name}, {{name!}}, {{123}}, {{user_name}}')).toEqual([]);
|
||||
});
|
||||
|
||||
it('extracts repeated variables', () => {
|
||||
expect(extractPromptVariables('Repeat: {{foo}}, again: {{foo}}')).toEqual(['foo', 'foo']);
|
||||
});
|
||||
|
||||
it('works with adjacent variables', () => {
|
||||
expect(extractPromptVariables('{{a}}{{b}}{{c}}')).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('returns empty array for empty string', () => {
|
||||
expect(extractPromptVariables('')).toEqual([]);
|
||||
});
|
||||
|
||||
it('extracts variables at string boundaries', () => {
|
||||
expect(extractPromptVariables('{{start}} middle {{end}}')).toEqual(['start', 'end']);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
/** Extracts a list of variables (as string[]) from a target string.
|
||||
* Note: Variables are wrapped in {{...}} and can include a-zA-Z, hyphen, and dot inside curly braces.
|
||||
* */
|
||||
export function extractPromptVariables(target: string): string[] {
|
||||
const regex = /\{\{([a-zA-Z.-]+)\}\}/g;
|
||||
const result: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = regex.exec(target)) !== null) {
|
||||
result.push(match[1]);
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,29 +1,29 @@
|
|||
/** Represents prompt variable type. */
|
||||
export const PromptVariableType = {
|
||||
BLOCK: 'block',
|
||||
BLOCK_TITLE: 'block.title',
|
||||
BLOCK_DESCRIPTION: 'block.description',
|
||||
BLOCK_CONTENTS: 'block.contents',
|
||||
// BLOCK_TITLE: 'block.title',
|
||||
// BLOCK_DESCRIPTION: 'block.description',
|
||||
// BLOCK_CONTENTS: 'block.contents',
|
||||
|
||||
OSS: 'oss',
|
||||
OSS_CONTENTS: 'oss.contents',
|
||||
OSS_ALIAS: 'oss.alias',
|
||||
OSS_TITLE: 'oss.title',
|
||||
OSS_DESCRIPTION: 'oss.description',
|
||||
// OSS_CONTENTS: 'oss.contents',
|
||||
// OSS_ALIAS: 'oss.alias',
|
||||
// OSS_TITLE: 'oss.title',
|
||||
// OSS_DESCRIPTION: 'oss.description',
|
||||
|
||||
SCHEMA: 'schema',
|
||||
SCHEMA_ALIAS: 'schema.alias',
|
||||
SCHEMA_TITLE: 'schema.title',
|
||||
SCHEMA_DESCRIPTION: 'schema.description',
|
||||
SCHEMA_THESAURUS: 'schema.thesaurus',
|
||||
SCHEMA_GRAPH: 'schema.graph',
|
||||
SCHEMA_TYPE_GRAPH: 'schema.type-graph',
|
||||
// SCHEMA_ALIAS: 'schema.alias',
|
||||
// SCHEMA_TITLE: 'schema.title',
|
||||
// SCHEMA_DESCRIPTION: 'schema.description',
|
||||
// SCHEMA_THESAURUS: 'schema.thesaurus',
|
||||
// SCHEMA_GRAPH: 'schema.graph',
|
||||
// SCHEMA_TYPE_GRAPH: 'schema.type-graph',
|
||||
|
||||
CONSTITUENTA: 'constituent',
|
||||
CONSTITUENTA_ALIAS: 'constituent.alias',
|
||||
CONSTITUENTA_CONVENTION: 'constituent.convention',
|
||||
CONSTITUENTA_DEFINITION: 'constituent.definition',
|
||||
CONSTITUENTA_DEFINITION_FORMAL: 'constituent.definition-formal',
|
||||
CONSTITUENTA_EXPRESSION_TREE: 'constituent.expression-tree'
|
||||
CONSTITUENTA: 'constituenta'
|
||||
// CONSTITUENTA_ALIAS: 'constituent.alias',
|
||||
// CONSTITUENTA_CONVENTION: 'constituent.convention',
|
||||
// CONSTITUENTA_DEFINITION: 'constituent.definition',
|
||||
// CONSTITUENTA_DEFINITION_FORMAL: 'constituent.definition-formal',
|
||||
// CONSTITUENTA_EXPRESSION_TREE: 'constituent.expression-tree'
|
||||
} as const;
|
||||
export type PromptVariableType = (typeof PromptVariableType)[keyof typeof PromptVariableType];
|
||||
|
|
64
rsconcept/frontend/src/features/ai/stores/ai-context.ts
Normal file
64
rsconcept/frontend/src/features/ai/stores/ai-context.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { create } from 'zustand';
|
||||
|
||||
import { type IBlock, type IOperationSchema } from '@/features/oss/models/oss';
|
||||
import { type IConstituenta, type IRSForm } from '@/features/rsform';
|
||||
|
||||
import { PromptVariableType } from '../models/prompting';
|
||||
|
||||
interface AIContextStore {
|
||||
currentOSS: IOperationSchema | null;
|
||||
setCurrentOSS: (value: IOperationSchema | null) => void;
|
||||
|
||||
currentSchema: IRSForm | null;
|
||||
setCurrentSchema: (value: IRSForm | null) => void;
|
||||
|
||||
currentBlock: IBlock | null;
|
||||
setCurrentBlock: (value: IBlock | null) => void;
|
||||
|
||||
currentConstituenta: IConstituenta | null;
|
||||
setCurrentConstituenta: (value: IConstituenta | null) => void;
|
||||
}
|
||||
|
||||
export const useAIStore = create<AIContextStore>()(set => ({
|
||||
currentOSS: null,
|
||||
setCurrentOSS: value => set({ currentOSS: value }),
|
||||
|
||||
currentSchema: null,
|
||||
setCurrentSchema: value => set({ currentSchema: value }),
|
||||
|
||||
currentBlock: null,
|
||||
setCurrentBlock: value => set({ currentBlock: value }),
|
||||
|
||||
currentConstituenta: null,
|
||||
setCurrentConstituenta: value => set({ currentConstituenta: value })
|
||||
}));
|
||||
|
||||
/** Returns a selector function for Zustand based on variable type */
|
||||
export function makeVariableSelector(variableType: PromptVariableType) {
|
||||
switch (variableType) {
|
||||
case PromptVariableType.OSS:
|
||||
return (state: AIContextStore) => ({ currentOSS: state.currentOSS });
|
||||
case PromptVariableType.SCHEMA:
|
||||
return (state: AIContextStore) => ({ currentSchema: state.currentSchema });
|
||||
case PromptVariableType.BLOCK:
|
||||
return (state: AIContextStore) => ({ currentBlock: state.currentBlock });
|
||||
case PromptVariableType.CONSTITUENTA:
|
||||
return (state: AIContextStore) => ({ currentConstituenta: state.currentConstituenta });
|
||||
default:
|
||||
return () => ({});
|
||||
}
|
||||
}
|
||||
|
||||
/** Evaluates a prompt variable */
|
||||
export function evaluatePromptVariable(variableType: PromptVariableType, context: Partial<AIContextStore>): string {
|
||||
switch (variableType) {
|
||||
case PromptVariableType.OSS:
|
||||
return context.currentOSS?.title ?? '';
|
||||
case PromptVariableType.SCHEMA:
|
||||
return context.currentSchema?.title ?? '';
|
||||
case PromptVariableType.BLOCK:
|
||||
return context.currentBlock?.title ?? '';
|
||||
case PromptVariableType.CONSTITUENTA:
|
||||
return context.currentConstituenta?.alias ?? '';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { PromptVariableType } from '../models/prompting';
|
||||
|
||||
import { useAIStore } from './ai-context';
|
||||
|
||||
export function useAvailableVariables(): PromptVariableType[] {
|
||||
const hasCurrentOSS = useAIStore(state => !!state.currentOSS);
|
||||
const hasCurrentSchema = useAIStore(state => !!state.currentSchema);
|
||||
const hasCurrentBlock = useAIStore(state => !!state.currentBlock);
|
||||
const hasCurrentConstituenta = useAIStore(state => !!state.currentConstituenta);
|
||||
|
||||
return [
|
||||
...(hasCurrentOSS ? [PromptVariableType.OSS] : []),
|
||||
...(hasCurrentSchema ? [PromptVariableType.SCHEMA] : []),
|
||||
...(hasCurrentBlock ? [PromptVariableType.BLOCK] : []),
|
||||
...(hasCurrentConstituenta ? [PromptVariableType.CONSTITUENTA] : [])
|
||||
];
|
||||
}
|
|
@ -123,21 +123,20 @@ export function applyFilterCategory(start: IConstituenta, schema: IRSForm): ICon
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix for alias indicating {@link CstType}.
|
||||
*/
|
||||
const cstTypePrefixRecord: Record<CstType, string> = {
|
||||
[CstType.BASE]: 'X',
|
||||
[CstType.CONSTANT]: 'C',
|
||||
[CstType.STRUCTURED]: 'S',
|
||||
[CstType.AXIOM]: 'A',
|
||||
[CstType.TERM]: 'D',
|
||||
[CstType.FUNCTION]: 'F',
|
||||
[CstType.PREDICATE]: 'P',
|
||||
[CstType.THEOREM]: 'T'
|
||||
};
|
||||
|
||||
/** Prefix for alias indicating {@link CstType}. */
|
||||
export function getCstTypePrefix(type: CstType): string {
|
||||
// prettier-ignore
|
||||
switch (type) {
|
||||
case CstType.BASE: return 'X';
|
||||
case CstType.CONSTANT: return 'C';
|
||||
case CstType.STRUCTURED: return 'S';
|
||||
case CstType.AXIOM: return 'A';
|
||||
case CstType.TERM: return 'D';
|
||||
case CstType.FUNCTION: return 'F';
|
||||
case CstType.PREDICATE: return 'P';
|
||||
case CstType.THEOREM: return 'T';
|
||||
}
|
||||
return cstTypePrefixRecord[type];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue
Block a user