diff --git a/DSL/DMapper/hbs/get_entities.handlebars b/DSL/DMapper/hbs/get_entities.handlebars index 6f13caa14..0716fffda 100644 --- a/DSL/DMapper/hbs/get_entities.handlebars +++ b/DSL/DMapper/hbs/get_entities.handlebars @@ -1,7 +1,7 @@ { "entities": [ {{#each data.hits}} - {{#each this.fields.filtered_items.[0].response}} + {{#each this.fields.filtered_entities}} { "id": "{{{ this }}}", "name": "{{{ this }}}" diff --git a/DSL/DMapper/hbs/get_intent_ids.handlebars b/DSL/DMapper/hbs/get_intent_ids.handlebars deleted file mode 100644 index e4dca4e3f..000000000 --- a/DSL/DMapper/hbs/get_intent_ids.handlebars +++ /dev/null @@ -1,11 +0,0 @@ -{ - "totalCount": {{hits.total.value}}, - "response": [ - {{#each hits.hits}} - { - "id":"{{this._id}}" - } - {{#unless @last}},{{/unless}} - {{/each}} - ] -} diff --git a/DSL/DMapper/hbs/get_intents.handlebars b/DSL/DMapper/hbs/get_intents.handlebars new file mode 100644 index 000000000..3a0665517 --- /dev/null +++ b/DSL/DMapper/hbs/get_intents.handlebars @@ -0,0 +1,7 @@ +{ +"intents": [ +{{#each hits}} + "{{_source.intent}}"{{#unless @last}},{{/unless}} +{{/each}} +] +} diff --git a/DSL/DMapper/hbs/get_responses_dependencies.handlebars b/DSL/DMapper/hbs/get_responses_dependencies.handlebars index 5c9845149..73807e985 100644 --- a/DSL/DMapper/hbs/get_responses_dependencies.handlebars +++ b/DSL/DMapper/hbs/get_responses_dependencies.handlebars @@ -30,6 +30,5 @@ ] }{{#unless @last}},{{/unless}} {{/each}} -], -"totalCount": {{totalCount}} +] } \ No newline at end of file diff --git a/DSL/OpenSearch/deploy-opensearch.sh b/DSL/OpenSearch/deploy-opensearch.sh index 29a4bab31..d4c0463d8 100755 --- a/DSL/OpenSearch/deploy-opensearch.sh +++ b/DSL/OpenSearch/deploy-opensearch.sh @@ -23,7 +23,6 @@ curl -H "Content-Type: application/x-ndjson" -X PUT "$URL/intents" -ku "$AUTH" - if $MOCK_ALLOWED; then curl -H "Content-Type: application/x-ndjson" -X PUT "$URL/intents/_bulk" -ku "$AUTH" --data-binary "@mock/intents.json"; fi curl -L -X POST "$URL/_scripts/intent-with-name" -H 'Content-Type: application/json' --data-binary "@templates/intent-with-name.json" curl -L -X POST "$URL/_scripts/intents-with-examples-count" -H 'Content-Type: application/json' --data-binary "@templates/intents-with-examples-count.json" -curl -L -X POST "$URL/_scripts/intents-with-pagination" -H 'Content-Type: application/json' -H 'Cookie: customJwtCookie=test' --data-binary "@templates/intents-with-pagination.json" # rules curl -XDELETE "$URL/rules?ignore_unavailable=true" -u "$AUTH" --insecure @@ -86,8 +85,8 @@ if $MOCK_ALLOWED; then curl -H "Content-Type: application/x-ndjson" -X PUT "$URL #Domain curl -XDELETE "$URL/domain?ignore_unavailable=true" -u "$AUTH" --insecure curl -H "Content-Type: application/x-ndjson" -X PUT "$URL/domain" -ku "$AUTH" --data-binary "@fieldMappings/domain.json" -curl -L -X POST "$URL/_scripts/domain-objects-with-pagination" -H 'Content-Type: application/json' -H 'Cookie: customJwtCookie=test' --data-binary "@templates/domain-objects-with-pagination.json" if $MOCK_ALLOWED; then curl -H "Content-Type: application/x-ndjson" -X PUT "$URL/domain/_bulk" -ku "$AUTH" --data-binary "@mock/domain.json"; fi +curl -L -X POST "$URL/_scripts/entities-with-pagination" -H 'Content-Type: application/json' -H 'Cookie: customJwtCookie=test' --data-binary "@templates/entities-with-pagination.json" #Config curl -XDELETE "$URL/config?ignore_unavailable=true" -u "$AUTH" --insecure diff --git a/DSL/OpenSearch/templates/domain-objects-with-pagination.json b/DSL/OpenSearch/templates/domain-objects-with-pagination.json deleted file mode 100644 index b2f2fb89e..000000000 --- a/DSL/OpenSearch/templates/domain-objects-with-pagination.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "script": { - "lang": "mustache", - "source": "{\"query\":{\"term\":{\"_id\":\"{{type}}\"}},\"script_fields\":{\"filtered_items\":{\"script\":{\"lang\":\"painless\",\"source\":\"def items = params._source.{{type}}; def isMap = items instanceof Map; def itemSet = isMap ? items.keySet() : items; def searchTerm = '{{filter}}'.toLowerCase(); int from = {{from}}; int size = {{size}}; def filtered = itemSet.findAll(item -> item.toLowerCase().contains(searchTerm)); int start = (int)Math.min(from, filtered.size()); int end = (int)Math.min(from + size, filtered.size()); Map result = new HashMap(); result.put(\\\"totalCount\\\", filtered.size()); result.put(\\\"response\\\", filtered.subList(start, end)); return result;\"}}}}" - } -} diff --git a/DSL/OpenSearch/templates/entities-with-pagination.json b/DSL/OpenSearch/templates/entities-with-pagination.json new file mode 100644 index 000000000..9b0c5c7d4 --- /dev/null +++ b/DSL/OpenSearch/templates/entities-with-pagination.json @@ -0,0 +1,6 @@ +{ + "script": { + "lang": "mustache", + "source": "{\"query\":{\"term\":{\"_id\":\"entities\"}},\"script_fields\":{\"filtered_entities\":{\"script\":{\"lang\":\"painless\",\"source\":\"def entities = params._source.entities; def searchTerm = '{{filter}}'; int from = {{from}}; int size = {{size}}; def filtered = entities.findAll(entity -> entity.toLowerCase().contains(searchTerm.toLowerCase())); int start = (int)Math.min(from, filtered.size()); int end = (int)Math.min(from + size, filtered.size()); return filtered.subList(start, end);\"}}}}" + } +} diff --git a/DSL/OpenSearch/templates/intents-with-pagination.json b/DSL/OpenSearch/templates/intents-with-pagination.json deleted file mode 100644 index c232a65d9..000000000 --- a/DSL/OpenSearch/templates/intents-with-pagination.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "script": { - "lang": "mustache", - "source": { - "from": "{{from}}", - "size": "{{size}}", - "track_total_hits": true, - "_source": ["intent"], - "query": { - "regexp": { - "intent": { - "value": ".*{{filter}}.*", - "case_insensitive": true, - "flags": "ALL" - } - } - } - }, - "params": { - "from": 0, - "size": 10, - "filter": "" - } - } -} diff --git a/DSL/OpenSearch/templates/responses-with-pagination.json b/DSL/OpenSearch/templates/responses-with-pagination.json index 6d2c74ecf..3a7914a3a 100644 --- a/DSL/OpenSearch/templates/responses-with-pagination.json +++ b/DSL/OpenSearch/templates/responses-with-pagination.json @@ -1,6 +1,6 @@ { "script": { "lang": "mustache", - "source": "{\"query\":{\"term\":{\"_id\":\"responses\"}},\"script_fields\":{\"filtered_responses\":{\"script\":{\"lang\":\"painless\",\"source\":\"def responses = params._source.responses; def searchTerm = '{{filter}}'.toLowerCase(); int from = {{from}}; int size = {{size}}; def filteredResponses = new HashMap(); for (def entry : responses.entrySet()) { def responseName = entry.getKey(); if (searchTerm == '' || responseName.toLowerCase().contains(searchTerm)) { filteredResponses.put(responseName, entry.getValue()); } } int start = (int)Math.min(from, filteredResponses.size()); int end = (int)Math.min(from + size, filteredResponses.size()); def paginatedResponses = new HashMap(); int counter = 0; for (def entry : filteredResponses.entrySet()) { if (counter >= start && counter < end) { paginatedResponses.put(entry.getKey(), entry.getValue()); } counter++; } return paginatedResponses;\"}},\"total_count\":{\"script\":{\"lang\":\"painless\",\"source\":\"def responses = params._source.responses; def searchTerm = '{{filter}}'.toLowerCase(); def filteredResponses = new HashMap(); for (def entry : responses.entrySet()) { def responseName = entry.getKey(); if (searchTerm == '' || responseName.toLowerCase().contains(searchTerm)) { filteredResponses.put(responseName, entry.getValue()); } } return (int)filteredResponses.size();\"}}}}" + "source": "{\"query\":{\"term\":{\"_id\":\"responses\"}},\"script_fields\":{\"filtered_responses\":{\"script\":{\"lang\":\"painless\",\"source\":\"def responses = params._source.responses; def searchTerm = '{{filter}}'; int from = {{from}}; int size = {{size}}; def filteredResponses = new HashMap(); for (def entry : responses.entrySet()) { def responseName = entry.getKey(); if (searchTerm == '' || responseName.toLowerCase().contains(searchTerm.toLowerCase())) { filteredResponses.put(responseName, entry.getValue()); } } int start = (int)Math.min(from, filteredResponses.size()); int end = (int)Math.min(from + size, filteredResponses.size()); def result = new HashMap(); int counter = 0; for (def entry : filteredResponses.entrySet()) { if (counter >= start && counter < end) { result.put(entry.getKey(), entry.getValue()); } counter++; } return result;\"}}}}" } } diff --git a/DSL/Ruuter.private/GET/rasa/entities.yml b/DSL/Ruuter.private/GET/rasa/entities.yml index acbd748a0..ce76fb685 100644 --- a/DSL/Ruuter.private/GET/rasa/entities.yml +++ b/DSL/Ruuter.private/GET/rasa/entities.yml @@ -22,12 +22,8 @@ getEntities: args: url: "[#TRAINING_OPENSEARCH]/domain/_search/template" body: - id: "domain-objects-with-pagination" - params: - type: "entities" - filter: ${incoming.params.filter} - from: ${incoming.params.from} - size: ${incoming.params.size} + id: "entities-with-pagination" + params: ${incoming.params} result: getEntitiesResult mapEntitiesData: diff --git a/DSL/Ruuter.private/GET/rasa/forms-list.yml b/DSL/Ruuter.private/GET/rasa/forms-list.yml deleted file mode 100644 index e25e3a40a..000000000 --- a/DSL/Ruuter.private/GET/rasa/forms-list.yml +++ /dev/null @@ -1,36 +0,0 @@ -declaration: - call: declare - version: 0.1 - description: "Decription placeholder for 'FORMS'" - method: get - accepts: json - returns: json - namespace: training - params: - - field: filter - type: string - description: "Query string" - - field: from - type: string - description: "Item number" - - field: size - type: string - description: "Page size" - -getForms: - call: http.post - args: - url: "[#TRAINING_OPENSEARCH]/domain/_search/template" - body: - id: "domain-objects-with-pagination" - params: - type: "forms" - filter: ${incoming.params.filter} - from: ${incoming.params.from} - size: ${incoming.params.size} - result: getFormsResult - -returnSuccess: - return: ${getFormsResult.response.body.hits.hits[0].fields.filtered_items[0]} - wrapper: false - next: end diff --git a/DSL/Ruuter.private/GET/rasa/intent-ids.yml b/DSL/Ruuter.private/GET/rasa/intent-ids.yml deleted file mode 100644 index 57bb15f4c..000000000 --- a/DSL/Ruuter.private/GET/rasa/intent-ids.yml +++ /dev/null @@ -1,44 +0,0 @@ -declaration: - call: declare - version: 0.1 - description: "Decription placeholder for 'INTENT-IDS'" - method: get - accepts: json - returns: json - namespace: training - params: - - field: filter - type: string - description: "Query string" - - field: from - type: string - description: "Item number" - - field: size - type: string - description: "Page size" - -getIntents: - call: http.post - args: - url: "[#TRAINING_OPENSEARCH]/intents/_search/template" - body: - id: "intents-with-pagination" - params: ${incoming.params} - result: getIntentsResult - -mapIntentsData: - call: http.post - args: - url: "[#TRAINING_DMAPPER]/hbs/training/get_intent_ids" - headers: - type: "json" - body: - hits: ${getIntentsResult.response.body.hits} - result: intentsData - next: returnSuccess - -returnSuccess: - return: ${intentsData.response.body} - # todo - wrapper: false - next: end diff --git a/DSL/Ruuter.private/GET/rasa/intents.yml b/DSL/Ruuter.private/GET/rasa/intents.yml new file mode 100644 index 000000000..c6bf8d776 --- /dev/null +++ b/DSL/Ruuter.private/GET/rasa/intents.yml @@ -0,0 +1,30 @@ +declaration: + call: declare + version: 0.1 + description: "Decription placeholder for 'INTENTS'" + method: get + accepts: json + returns: json + namespace: training + +getIntents: + call: http.get + args: + url: "[#TRAINING_OPENSEARCH]/intents/_search?size=10000" + result: getIntentsResult + +mapIntentsData: + call: http.post + args: + url: "[#TRAINING_DMAPPER]/hbs/training/get_intents" + headers: + type: 'json' + body: + hits: ${getIntentsResult.response.body.hits.hits} + result: intentsData + next: returnSuccess + +returnSuccess: + return: ${intentsData.response.body.intents} + wrapper: false + next: end diff --git a/DSL/Ruuter.private/GET/rasa/responses-list.yml b/DSL/Ruuter.private/GET/rasa/responses-list.yml new file mode 100644 index 000000000..5533646fb --- /dev/null +++ b/DSL/Ruuter.private/GET/rasa/responses-list.yml @@ -0,0 +1,76 @@ +declaration: + call: declare + version: 0.1 + description: "Description placeholder for 'RESPONSES-LIST'" + method: get + accepts: json + returns: json + namespace: training + params: + - field: filter + type: string + description: "Query string" + - field: from + type: string + description: "Item number" + - field: size + type: string + description: "Page size" + +getResponses: + call: http.post + args: + url: "[#TRAINING_OPENSEARCH]/domain/_search/template" + body: + id: "responses-with-pagination" + params: ${incoming.params} + result: getResponsesResult + +mapResponsesNames: + call: http.post + args: + url: "[#TRAINING_DMAPPER]/hbs/training/get_responses_names" + headers: + type: "json" + body: + data: ${getResponsesResult.response.body.hits.hits[0].fields.filtered_responses[0]} + result: responsesNames + +getStories: + call: http.post + args: + url: "[#TRAINING_OPENSEARCH]/stories/_search/template" + body: + id: "stories-by-responses" + params: + responses: "${responsesNames.response.body}" + size: 1000 + result: getStoriesResult + +getRules: + call: http.post + args: + url: "[#TRAINING_OPENSEARCH]/rules/_search/template" + body: + id: "rules-by-responses" + params: + responses: "${responsesNames.response.body}" + size: 1000 + result: getRulesResult + +mapResponsesData: + call: http.post + args: + url: "[#TRAINING_DMAPPER]/hbs/training/get_responses_dependencies" + headers: + type: "json" + body: + rules: ${getRulesResult.response.body.hits.hits} + stories: ${getStoriesResult.response.body.hits.hits} + responses: ${getResponsesResult.response.body.hits.hits[0].fields.filtered_responses[0]} + result: responsesData + +returnSuccess: + return: ${responsesData.response.body} + wrapper: false + next: end diff --git a/DSL/Ruuter.private/GET/rasa/responses.yml b/DSL/Ruuter.private/GET/rasa/responses.yml index c301ae325..c0dfad82a 100644 --- a/DSL/Ruuter.private/GET/rasa/responses.yml +++ b/DSL/Ruuter.private/GET/rasa/responses.yml @@ -1,77 +1,30 @@ declaration: call: declare version: 0.1 - description: "Description placeholder for 'responses'" + description: "Decription placeholder for 'RESPONSES'" method: get accepts: json returns: json namespace: training - params: - - field: filter - type: string - description: "Query string" - - field: from - type: string - description: "Item number" - - field: size - type: string - description: "Page size" getResponses: - call: http.post + call: http.get args: - url: "[#TRAINING_OPENSEARCH]/domain/_search/template" - body: - id: "responses-with-pagination" - params: ${incoming.params} + url: "[#TRAINING_OPENSEARCH]/domain/_search?size=10000" result: getResponsesResult -mapResponsesNames: - call: http.post - args: - url: "[#TRAINING_DMAPPER]/hbs/training/get_responses_names" - headers: - type: "json" - body: - data: ${getResponsesResult.response.body.hits.hits[0].fields.filtered_responses[0]} - result: responsesNames - -getStories: - call: http.post - args: - url: "[#TRAINING_OPENSEARCH]/stories/_search/template" - body: - id: "stories-by-responses" - params: - responses: "${responsesNames.response.body}" - size: 1000 - result: getStoriesResult - -getRules: - call: http.post - args: - url: "[#TRAINING_OPENSEARCH]/rules/_search/template" - body: - id: "rules-by-responses" - params: - responses: "${responsesNames.response.body}" - size: 1000 - result: getRulesResult - mapResponsesData: call: http.post args: - url: "[#TRAINING_DMAPPER]/hbs/training/get_responses_dependencies" + url: "[#TRAINING_DMAPPER]/hbs/training/get_responses" headers: - type: "json" + type: 'json' body: - rules: ${getRulesResult.response.body.hits.hits} - stories: ${getStoriesResult.response.body.hits.hits} - responses: ${getResponsesResult.response.body.hits.hits[0].fields.filtered_responses[0]} - totalCount: ${getResponsesResult.response.body.hits.hits[0].fields.total_count[0]} + hits: ${getResponsesResult.response.body.hits.hits} result: responsesData + next: returnSuccess returnSuccess: - return: ${responsesData.response.body} + return: ${responsesData.response.body.responses} wrapper: false next: end diff --git a/DSL/Ruuter.private/GET/rasa/slots-list.yml b/DSL/Ruuter.private/GET/rasa/slots-list.yml deleted file mode 100644 index f83e42fa9..000000000 --- a/DSL/Ruuter.private/GET/rasa/slots-list.yml +++ /dev/null @@ -1,36 +0,0 @@ -declaration: - call: declare - version: 0.1 - description: "Decription placeholder for 'SLOTS'" - method: get - accepts: json - returns: json - namespace: training - params: - - field: filter - type: string - description: "Query string" - - field: from - type: string - description: "Item number" - - field: size - type: string - description: "Page size" - -getSlots: - call: http.post - args: - url: "[#TRAINING_OPENSEARCH]/domain/_search/template" - body: - id: "domain-objects-with-pagination" - params: - type: "slots" - filter: ${incoming.params.filter} - from: ${incoming.params.from} - size: ${incoming.params.size} - result: getSlotsResult - -returnSuccess: - return: ${getSlotsResult.response.body.hits.hits[0].fields.filtered_items[0]} - wrapper: false - next: end diff --git a/GUI/src/hooks/useInfinitePagination.ts b/GUI/src/hooks/useInfinitePagination.ts index dabeafe02..5da6cee70 100644 --- a/GUI/src/hooks/useInfinitePagination.ts +++ b/GUI/src/hooks/useInfinitePagination.ts @@ -1,9 +1,9 @@ import { useInfiniteQuery } from '@tanstack/react-query'; -import { PaginatedResponse, PaginationParams } from 'types/api'; +import { PaginationParams } from 'types/api'; interface UseInfinitePaginationProps { queryKey: string[]; - fetchFn: (params: PaginationParams) => Promise>; + fetchFn: (params: PaginationParams) => Promise<{ response: T[] }>; pageSize?: number; filter?: string; } @@ -14,7 +14,7 @@ export function useInfinitePagination({ pageSize = 50, filter = '', }: UseInfinitePaginationProps) { - return useInfiniteQuery>({ + return useInfiniteQuery<{ response: T[] }>({ queryKey: [...queryKey, filter], queryFn: ({ pageParam = 0 }) => fetchFn({ pageParam, pageSize, filter }), getNextPageParam: (lastPage, pages) => (lastPage.response.length === 0 ? undefined : pages.length * pageSize), diff --git a/GUI/src/pages/HistoricalConversations/Appeals/index.tsx b/GUI/src/pages/HistoricalConversations/Appeals/index.tsx index 29ea38551..8dc6dd23e 100644 --- a/GUI/src/pages/HistoricalConversations/Appeals/index.tsx +++ b/GUI/src/pages/HistoricalConversations/Appeals/index.tsx @@ -8,14 +8,11 @@ import { MdDeleteOutline, MdOutlineSave } from 'react-icons/md'; import { Button, Card, DataTable, Dialog, FormInput, FormSelect, Icon, Track } from 'components'; import { Appeal } from 'types/appeal'; -import { Intent, IntentId } from 'types/intent'; +import { Intent } from 'types/intent'; import { addAppeal, deleteAppeal } from 'services/appeals'; import { useToast } from 'hooks/useToast'; import i18n from '../../../../i18n'; import withAuthorization, { ROLES } from 'hoc/with-authorization'; -import { useInfinitePagination } from 'hooks/useInfinitePagination'; -import { getIntentIds } from 'services/intents'; -import { flattenPaginatedData } from 'utils/api-utils'; const Appeals: FC = () => { const { t } = useTranslation(); @@ -27,18 +24,15 @@ const Appeals: FC = () => { const { data: appeals } = useQuery({ queryKey: ['appeals'], }); - const { data } = useInfinitePagination({ - queryKey: ['intent-ids', filter], - fetchFn: getIntentIds, - filter, - pageSize: 1000, + const { data: intents } = useQuery({ + queryKey: ['intents'], }); - const intents = useMemo(() => flattenPaginatedData(data), [data]); const { register, handleSubmit } = useForm<{ message: string }>(); const newAppealMutation = useMutation({ mutationFn: (data: { message: string }) => addAppeal(data), onSuccess: async () => { + await queryClient.invalidateQueries(['responses']); setAddFormVisible(false); toast.open({ type: 'success', @@ -79,10 +73,8 @@ const Appeals: FC = () => { onSettled: () => setDeletableAppeal(null), }); - const appealsColumns = useMemo( - () => getColumns(handleNewAppealSubmit, setDeletableAppeal, intents), - [handleNewAppealSubmit, intents] - ); + + const appealsColumns = useMemo(() => getColumns(handleNewAppealSubmit, setDeletableAppeal, intents), [intents]); if (!appeals) return <>Loading...; @@ -95,34 +87,43 @@ const Appeals: FC = () => { setFilter(e.target.value)} /> - + {addFormVisible && ( - +
- +
- +
)} - - + + {deletableAppeal !== null && ( @@ -131,10 +132,11 @@ const Appeals: FC = () => { onClose={() => setDeletableAppeal(null)} footer={ <> - - + @@ -150,7 +152,7 @@ const Appeals: FC = () => { const getColumns = ( handleNewAppealSubmit: () => void, setDeletableAppeal: (id: number) => void, - intents?: IntentId[] + intents?: Intent[], ) => { const columnHelper = createColumnHelper(); @@ -161,7 +163,7 @@ const getColumns = ( ), @@ -170,15 +172,13 @@ const getColumns = ( header: i18n.t('training.intents.title') || '', cell: (props) => ( ({ - label: intent.id, - value: intent.id, - })) || [] - } + options={intents?.map((intent) => ({ + label: intent.intent, + value: intent.intent, + })) || []} /> ), }), @@ -188,17 +188,23 @@ const getColumns = ( size: '1%', }, cell: (props) => ( - - ), + ) }), columnHelper.display({ header: '', cell: (props) => ( - ), @@ -207,8 +213,8 @@ const getColumns = ( size: '1%', }, }), - ]; -}; + ] +} export default withAuthorization(Appeals, [ ROLES.ROLE_ADMINISTRATOR, diff --git a/GUI/src/pages/Training/Intents/NodeList.tsx b/GUI/src/pages/Training/Intents/NodeList.tsx deleted file mode 100644 index 1d1b83689..000000000 --- a/GUI/src/pages/Training/Intents/NodeList.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import Track from 'components/Track'; -import { useInfinitePagination } from 'hooks/useInfinitePagination'; -import { ReactNode, useMemo, useRef, useCallback } from 'react'; -import { flattenPaginatedData } from 'utils/api-utils'; -import Collapsible from 'components/Collapsible'; - -export interface NodeListProps { - queryKey: string[]; - fetchFn: (params: any) => Promise; - filter?: string; - title: string; - defaultOpen?: boolean; - renderItem: (item: T, ref?: (element: HTMLElement | null) => void) => ReactNode; -} - -function NodeList({ queryKey, fetchFn, filter = '', title, defaultOpen, renderItem }: NodeListProps) { - const { data, fetchNextPage, isFetching, isLoading, hasNextPage } = useInfinitePagination({ - queryKey, - fetchFn, - filter, - }); - - const items = useMemo(() => flattenPaginatedData(data), [data]); - const observer = useRef(null); - - const lastElement = useCallback( - (element: HTMLElement | null) => { - if (isLoading) return; - if (observer.current) observer.current.disconnect(); - - observer.current = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting && hasNextPage && !isFetching) { - fetchNextPage(); - } - }); - - if (element) observer.current.observe(element); - }, - [isLoading, hasNextPage, isFetching, fetchNextPage] - ); - - return ( - - - {items.map((item, index) => renderItem(item, index === items.length - 1 ? lastElement : undefined))} - - - ); -} - -export default NodeList; diff --git a/GUI/src/pages/Training/Responses/index.tsx b/GUI/src/pages/Training/Responses/index.tsx index d79714418..d609ae928 100644 --- a/GUI/src/pages/Training/Responses/index.tsx +++ b/GUI/src/pages/Training/Responses/index.tsx @@ -31,7 +31,7 @@ const Responses: FC = () => { const { filter, setFilter } = useDebouncedFilter(); const [responses, setResponses] = useState([]); const { data, refetch, fetchNextPage, isFetching } = useInfinitePagination({ - queryKey: ['responses', filter], + queryKey: ['responses-list', filter], fetchFn: getResponses, filter, }); @@ -56,11 +56,11 @@ const Responses: FC = () => { const responseSaveMutation = useMutation({ mutationFn: ({ id, text }: { id: string; text: string }) => editResponse(id, text), onMutate: async () => { - await queryClient.cancelQueries(['responses', filter]); + await queryClient.cancelQueries(['responses-list', filter]); setRefreshing(true); }, onSuccess: async () => { - await queryClient.invalidateQueries(['responses', filter]); + await queryClient.invalidateQueries(['responses-list', filter]); toast.open({ type: 'success', title: t('global.notification'), @@ -85,7 +85,7 @@ const Responses: FC = () => { const responseDeleteMutation = useMutation({ mutationFn: (data: { response: string }) => deleteResponse(data), onMutate: async () => { - await queryClient.invalidateQueries(['responses', filter]); + await queryClient.invalidateQueries(['responses-list', filter]); setRefreshing(true); }, onSuccess: async () => { @@ -116,7 +116,7 @@ const Responses: FC = () => { mutationFn: ({ name, text }: { name: string; text: string }) => editResponse('utter_' + name.trim().replace(/\s+/g, '_'), text, false), onMutate: async () => { - await queryClient.invalidateQueries(['responses', filter]); + await queryClient.invalidateQueries(['responses-list', filter]); setRefreshing(true); }, onSuccess: async () => { @@ -263,7 +263,7 @@ const Responses: FC = () => { maxLength={RESPONSE_TEXT_LENGTH} showMaxLength onChange={(e) => { - if (!withBackSlash.test(e.target.value) && !e.target.value.startsWith(' ')) { + if(!withBackSlash.test(e.target.value) && !e.target.value.startsWith(' ')) { field.onChange(e.target.value); setEditingTrainingTitle(e.target.value); } else { @@ -346,25 +346,25 @@ const buildTextCell = (value: string, isEditable: boolean, onEdit: (value: strin if (isEditable) { return ( - { - const userInput = e.target.value; + { + const userInput = e.target.value; - if (withBackSlash.test(userInput)) { - e.target.value = lastValidValue; - } else { - lastValidValue = userInput; - onEdit(userInput); - } - }} - /> + if (withBackSlash.test(userInput)) { + e.target.value = lastValidValue; + } else { + lastValidValue = userInput; + onEdit(userInput); + } + }} + /> ); } diff --git a/GUI/src/pages/Training/Rules/RulesDetail.tsx b/GUI/src/pages/Training/Rules/RulesDetail.tsx index bdf1f4ba0..db60c1e83 100644 --- a/GUI/src/pages/Training/Rules/RulesDetail.tsx +++ b/GUI/src/pages/Training/Rules/RulesDetail.tsx @@ -7,8 +7,10 @@ import { useNavigate, useParams, useLocation } from 'react-router-dom'; import { AxiosError } from 'axios'; import 'reactflow/dist/style.css'; -import { Box, Button, Card, Collapsible, Dialog, FormInput, Icon, Track } from 'components'; -import type { Response } from 'types/response'; +import { Box, Button, Collapsible, Dialog, FormInput, Icon, Track } from 'components'; +import { Responses } from 'types/response'; +import { Form } from 'types/form'; +import { Slot } from 'types/slot'; import { useToast } from 'hooks/useToast'; import { addRule, deleteRule, editRule } from 'services/rules'; import CustomNode from './CustomNode'; @@ -19,13 +21,6 @@ import LoadingDialog from '../../../components/LoadingDialog'; import { Rule, RuleDTO } from '../../../types/rule'; import withAuthorization, { ROLES } from 'hoc/with-authorization'; import './RulesDetail.scss'; -import { useDebouncedFilter } from 'hooks/useDebouncedFilter'; -import { getResponses } from 'services/responses'; -import NodeList from 'pages/Training/Intents/NodeList'; -import { getIntentIds } from 'services/intents'; -import { IntentId } from 'types/intent'; -import { getForms } from 'services/forms'; -import { getSlots } from 'services/slots'; const nodeTypes = { customNode: CustomNode, @@ -48,20 +43,6 @@ const initialNodes: Node[] = [ }, ]; -// More conditions to be added in the future -const conditions = [{ label: '', text: 'condition', type: 'conditionNode', className: 'condition' }]; - -const actions = [ - { label: 'Checkpoints:', text: 'checkpoints', checkpoint: true }, - { label: 'conversation_start: true', text: 'conversation_start' }, - { label: 'action_listen', text: 'action_listen' }, - { label: 'action_restart', text: 'action_restart' }, - { label: 'wait_for_user_input: false', text: 'wait_for_user_input' }, -]; - -const filterByText = (items: Array<{ text: string }>, searchText: string) => - items.filter(({ text }) => !searchText || text.toLowerCase().includes(searchText.toLowerCase())); - const RulesDetail: FC<{ mode: 'new' | 'edit' }> = ({ mode }) => { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); @@ -87,7 +68,18 @@ const RulesDetail: FC<{ mode: 'new' | 'edit' }> = ({ mode }) => { enabled: !!currentEntityId, }); - const { filter, setFilter } = useDebouncedFilter(); + const { data: intents } = useQuery({ + queryKey: ['intents'], + }); + const { data: responses } = useQuery({ + queryKey: ['responses'], + }); + const { data: forms } = useQuery({ + queryKey: ['forms'], + }); + const { data: slots } = useQuery({ + queryKey: ['slots'], + }); useDocumentEscapeListener(() => setEditableTitle(null)); @@ -269,9 +261,6 @@ const RulesDetail: FC<{ mode: 'new' | 'edit' }> = ({ mode }) => { const title = currentEntityId || t('global.title'); - const filteredActions = filterByText(actions, filter); - const filteredConditions = filterByText(conditions, filter); - const handleGraphSave = async () => { const isRename = editableTitle && editableTitle !== id; if (!isRename) { @@ -359,138 +348,171 @@ const RulesDetail: FC<{ mode: 'new' | 'edit' }> = ({ mode }) => { align="stretch" style={{ maxHeight: 'calc(100vh - 100px)', overflow: 'auto', paddingBottom: '5vh' }} > - - setFilter(e.target.value)} - /> - - - + {category === 'rules' && ( + + + + + + )} + {intents && Array.isArray(intents) && ( + + + {intents.map((intent) => ( + + ))} + + + )} + + {responses && Array.isArray(responses) && ( + + + {responses.map((response, index) => ( + + ))} + + + )} + + {forms && Array.isArray(forms) && ( + + + {forms.map((form) => ( + + ))} + + + )} + + {slots && Array.isArray(slots) && ( + + + {slots.map((slot) => ( + + ))} + + + )} + + - {filteredConditions.map(({ label, text, type, className }) => ( + {category === 'rules' && ( - ))} - - - - - queryKey={['intent-ids', filter]} - fetchFn={getIntentIds} - filter={filter} - title={t('training.intents.title')} - defaultOpen - renderItem={(intent, ref) => ( - - )} - /> - - queryKey={['responses', filter]} - fetchFn={getResponses} - filter={filter} - title={t('training.responses.title')} - renderItem={(response, ref) => ( - - )} - /> - - queryKey={['forms-list', filter]} - fetchFn={getForms} - filter={filter} - title={t('training.forms.title')} - renderItem={(form, ref) => ( + )} + {category === 'rules' && ( + + )} - )} - /> - - queryKey={['slots-list', filter]} - fetchFn={getSlots} - filter={filter} - title={t('training.slots.title')} - renderItem={(slot, ref) => ( - )} - /> - - - - {filteredActions.map(({ label, text, checkpoint }) => ( + {category === 'rules' && ( - ))} + )} diff --git a/GUI/src/services/forms.ts b/GUI/src/services/forms.ts index aa2846148..6538b43fb 100644 --- a/GUI/src/services/forms.ts +++ b/GUI/src/services/forms.ts @@ -1,27 +1,17 @@ -import { PaginatedResponse, PaginationParams } from 'types/api'; import { rasaApi } from './api'; import { Form, FormCreateDTO, FormEditDTO } from 'types/form'; -export const getForms = async ({ - pageParam, - pageSize, - filter, -}: PaginationParams): Promise> => { - const { data } = await rasaApi.get(`/forms-list?size=${pageSize}&filter=${filter}&from=${pageParam}`); - return data; -}; - export async function createForm(formData: FormCreateDTO) { const { data } = await rasaApi.post
(`forms/add`, formData); return data; } -export async function editForm(form_name: string, form: FormEditDTO) { - const { data } = await rasaApi.post(`forms/update`, { form_name, form }); +export async function editForm(form_name: string , form: FormEditDTO) { + const { data } = await rasaApi.post(`forms/update`, {form_name,form}); return data; } export async function deleteForm(id: string | number) { - const { data } = await rasaApi.post(`forms/delete`, { form_name: id }); + const { data } = await rasaApi.post(`forms/delete`, {form_name: id}); return data; } diff --git a/GUI/src/services/intents.ts b/GUI/src/services/intents.ts index 6fa12590c..a7c6ba8d2 100644 --- a/GUI/src/services/intents.ts +++ b/GUI/src/services/intents.ts @@ -1,15 +1,4 @@ -import { IntentId } from 'types/intent'; -import { rasaApi } from './api'; -import { PaginatedResponse, PaginationParams } from 'types/api'; - -export const getIntentIds = async ({ - pageParam, - pageSize, - filter, -}: PaginationParams): Promise> => { - const { data } = await rasaApi.get(`/intent-ids?size=${pageSize}&filter=${filter}&from=${pageParam}`); - return data; -}; +import { fileApi, rasaApi, rasaApi } from './api'; export async function addIntent(newIntentData: { name: string }) { const { data } = await rasaApi.post('/intents/add', newIntentData); diff --git a/GUI/src/services/responses.ts b/GUI/src/services/responses.ts index 13f3fbfec..30fc74695 100644 --- a/GUI/src/services/responses.ts +++ b/GUI/src/services/responses.ts @@ -7,7 +7,7 @@ export const getResponses = async ({ pageSize, filter, }: PaginationParams): Promise<{ response: Response[] }> => { - const { data } = await rasaApi.get(`/responses?size=${pageSize}&filter=${filter}&from=${pageParam}`); + const { data } = await rasaApi.get(`/responses-list?size=${pageSize}&filter=${filter}&from=${pageParam}`); return data; }; diff --git a/GUI/src/services/slots.ts b/GUI/src/services/slots.ts index e9900a546..e6c37e36a 100644 --- a/GUI/src/services/slots.ts +++ b/GUI/src/services/slots.ts @@ -1,16 +1,6 @@ -import { PaginatedResponse, PaginationParams } from 'types/api'; import { rasaApi } from './api'; import { Slot, SlotCreateDTO, SlotEditDTO } from 'types/slot'; -export const getSlots = async ({ - pageParam, - pageSize, - filter, -}: PaginationParams): Promise> => { - const { data } = await rasaApi.get(`/slots-list?size=${pageSize}&filter=${filter}&from=${pageParam}`); - return data; -}; - export async function createSlot(formData: SlotCreateDTO) { const slot = validateSlot(formData); const { data } = await rasaApi.post(`slots/add`, slot); @@ -19,30 +9,28 @@ export async function createSlot(formData: SlotCreateDTO) { export async function editSlot(oldName: string, formData: SlotEditDTO) { const slot = validateSlot(formData); - const { data } = await rasaApi.post(`slots/update`, { oldName, slot }); + const { data } = await rasaApi.post(`slots/update`, {oldName,slot}); return data; } export async function deleteSlot(slot: string | number) { - const { data } = await rasaApi.post(`slots/delete`, { slotName: slot }); + const { data } = await rasaApi.post(`slots/delete`, {slotName: slot}); return data; } -const validateSlot = (formData: SlotCreateDTO | SlotEditDTO) => { +const validateSlot = (formData : SlotCreateDTO | SlotEditDTO) => { return { slot: { - [formData.name]: { - influence_conversation: formData.influenceConversation || false, + [formData.name] : { + influence_conversation : formData.influenceConversation || false, type: 'text', - mappings: [ - { - type: formData.mappings.type || 'from_text', - entity: formData.mappings.entity || 'test', - intent: formData.mappings.intent || [], - notIntent: formData.mappings.notIntent || [], - }, - ], - }, - }, - }; -}; + mappings: [{ + type: formData.mappings.type || 'from_text', + entity: formData.mappings.entity || 'test', + intent: formData.mappings.intent || [], + notIntent: formData.mappings.notIntent || [] + }] + } + } + } +} diff --git a/GUI/src/types/api.ts b/GUI/src/types/api.ts index 36fe399da..8f459a4a6 100644 --- a/GUI/src/types/api.ts +++ b/GUI/src/types/api.ts @@ -3,8 +3,3 @@ export interface PaginationParams { pageSize: number; filter: string; } - -export interface PaginatedResponse { - response: T[]; - totalCount?: number; -} diff --git a/GUI/src/types/intent.ts b/GUI/src/types/intent.ts index c01e2abf7..b2cc45ff6 100644 --- a/GUI/src/types/intent.ts +++ b/GUI/src/types/intent.ts @@ -9,5 +9,3 @@ export interface Intent { serviceId: string; isCommon?: boolean; } - -export type IntentId = Pick; diff --git a/GUI/src/types/response.ts b/GUI/src/types/response.ts index c7075ce2b..29a88ec90 100644 --- a/GUI/src/types/response.ts +++ b/GUI/src/types/response.ts @@ -1,3 +1,12 @@ +export interface Responses { + response: ResponseData[]; +} + +export interface ResponseData { + name: string; + response: ResponseDataResponse[]; +} + export interface ResponseDataResponse { text: string; condition?: Condition[]; @@ -18,6 +27,7 @@ export interface ResponseEdit { response: ResponseDataEdit; } +// TODO: unify and simplify types later, breaks Responses page currently export interface Response { response: string; text: string;