diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index cb69a3748..3ae1e444c 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -803,7 +803,6 @@
"__PLAN_PAGE_EXPERT_REVIEW_TOOLTIP_CONTENT": "An expert will contact you within 3-5 business days with a custom quote",
"__PLAN_PAGE_EXPERT_REVIEW_WARNING": "Requires expert review",
"__PLAN_PAGE_GROUP_TITLE_ACTIVITY_SCOPE": "Activity Scope",
- "__PLAN_PAGE_GROUP_TITLE_ADDITIONAL_DETAILS": "Additional Details",
"__PLAN_PAGE_GROUP_TITLE_ADVANCED_CRITERIA": "Other",
"__PLAN_PAGE_GROUP_TITLE_BEFORE_STARTING": " ",
"__PLAN_PAGE_GROUP_TITLE_BEHAVIOURAL_DATA": "Behavioural Data",
@@ -1014,6 +1013,32 @@
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_ACCESSIBILITY_TAB": "Accessibility",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_ACCESSIBILITY_TASK_ACCESSIBILITY_BUTTON": "Accessibility Task",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_ACCESSIBILITY_TASKS_LABEL": "Accessibility",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_AI_DISCLAIMER": "Want to speed things up? Generate structured functional tasks automatically based on your project details.",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ALERT_TEXT": "List the key pages or flows you want tested. Example:
- Homepage
- Product search
- Product page (add to cart, add to wishlist)
",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ALERT_TITLE": "How to describe your areas:",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_BUTTON": "Create functional tasks AI",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CANCEL": "Cancel",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_BODY": "Heads up! Anything you’ve typed or started generating will be lost if you close this modal.",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_CLOSE_ANYWAY": "Confirm",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_HEADER": "Are you sure you want to close?",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_KEEP_PROCESSING": "Cancel",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CREATE_BUTTON": "Generate tasks",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CREATING": "Creating...",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ERROR_FETCHING": "Something went wrong. Please try again.",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ERROR_SUBMITTING": "Something went wrong. Please try again.",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_FINALIZING": "I'm analyzing your setup and building realistic scenarios. Good things take a few seconds 🙂",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_GATHERING": "Gathering your data…",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_MODAL_HEADER": "Create functional tasks AI",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_PROMPT_INFO": "Tell us which areas you want tested.The AI will use your project goals and setup to generate relevant, structured tasks.",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_PROMPT_LABEL": "Describe the area to test",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_PROMPT_PLACEHOLDER": "e.g. Homepage, product search, checkout…",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_STOP": "Stop Generation",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_SUCCESS_TOAST": "Tasks successfully generated! Review them below and save your plan to move forward.",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_ANALYZING_CONTEXT": "Analyzing context",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_COLLECTING_INPUTS": "Collecting your inputs",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_GENERATING_TASKS": "Generating tasks",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_IDENTIFYING_AREAS": "Identifying key areas",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASKS_QUANTITY_LABEL": "Tasks to generate",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_DEFAULT_TAB": "All",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_EXPERIENTIAL_TAB": "Experience",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_EXPERIENTIAL_TASK_THINKING_ALOUD_BUTTON": "Thinking aloud task",
diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json
index 1ab3b65b9..811ab8442 100644
--- a/src/locales/it/translation.json
+++ b/src/locales/it/translation.json
@@ -833,7 +833,6 @@
"__PLAN_PAGE_EXPERT_REVIEW_TOOLTIP_CONTENT": "",
"__PLAN_PAGE_EXPERT_REVIEW_WARNING": "",
"__PLAN_PAGE_GROUP_TITLE_ACTIVITY_SCOPE": "",
- "__PLAN_PAGE_GROUP_TITLE_ADDITIONAL_DETAILS": "",
"__PLAN_PAGE_GROUP_TITLE_ADVANCED_CRITERIA": "",
"__PLAN_PAGE_GROUP_TITLE_BEFORE_STARTING": "",
"__PLAN_PAGE_GROUP_TITLE_BEHAVIOURAL_DATA": "",
@@ -1047,6 +1046,32 @@
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_ACCESSIBILITY_TAB": "",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_ACCESSIBILITY_TASK_ACCESSIBILITY_BUTTON": "",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_ACCESSIBILITY_TASKS_LABEL": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_AI_DISCLAIMER": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ALERT_TEXT": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ALERT_TITLE": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_BUTTON": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CANCEL": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_BODY": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_CLOSE_ANYWAY": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_HEADER": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_KEEP_PROCESSING": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CREATE_BUTTON": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CREATING": "Creating...",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ERROR_FETCHING": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ERROR_SUBMITTING": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_FINALIZING": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_GATHERING": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_MODAL_HEADER": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_PROMPT_INFO": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_PROMPT_LABEL": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_PROMPT_PLACEHOLDER": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_STOP": "Stop Generation",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_SUCCESS_TOAST": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_ANALYZING_CONTEXT": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_COLLECTING_INPUTS": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_GENERATING_TASKS": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_IDENTIFYING_AREAS": "",
+ "__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASKS_QUANTITY_LABEL": "",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_DEFAULT_TAB": "",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_EXPERIENTIAL_TAB": "",
"__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_EXPERIENTIAL_TASK_THINKING_ALOUD_BUTTON": "",
diff --git a/src/pages/Plan/common/constants.ts b/src/pages/Plan/common/constants.ts
index ec1f8ebb4..131200246 100644
--- a/src/pages/Plan/common/constants.ts
+++ b/src/pages/Plan/common/constants.ts
@@ -15,7 +15,7 @@ export const MODULE_GROUPS: Record = {
{
id: 'essentials',
title: '__PLAN_PAGE_GROUP_TITLE_ESSENTIALS',
- modules: ['goal'],
+ modules: ['goal', 'out_of_scope'],
},
{
id: 'technicalRequirements',
@@ -75,11 +75,6 @@ export const MODULE_GROUPS: Record = {
title: '__PLAN_PAGE_GROUP_TITLE_ACTIVITY_SCOPE',
modules: ['tasks'],
},
- {
- id: 'additionalDetails',
- title: '__PLAN_PAGE_GROUP_TITLE_ADDITIONAL_DETAILS',
- modules: ['out_of_scope'],
- },
],
summary: [],
};
@@ -99,7 +94,6 @@ const i18nPlanTitles = () => {
t('__PLAN_PAGE_GROUP_TITLE_BEHAVIOURAL_DATA');
t('__PLAN_PAGE_GROUP_TITLE_ADVANCED_CRITERIA');
t('__PLAN_PAGE_GROUP_TITLE_ACTIVITY_SCOPE');
- t('__PLAN_PAGE_GROUP_TITLE_ADDITIONAL_DETAILS');
// Tab titles
t('__PLAN_PAGE_TAB_SETUP_TAB_TITLE');
t('__PLAN_PAGE_TAB_TARGET_TAB_TITLE');
diff --git a/src/pages/Plan/modules/Factory/modules/Tasks/Component/context/index.tsx b/src/pages/Plan/modules/Factory/modules/Tasks/Component/context/index.tsx
index 046fecd16..c66c651cb 100644
--- a/src/pages/Plan/modules/Factory/modules/Tasks/Component/context/index.tsx
+++ b/src/pages/Plan/modules/Factory/modules/Tasks/Component/context/index.tsx
@@ -3,6 +3,8 @@ import { createContext, useContext, useMemo, useState } from 'react';
interface ModuleTasksContextType {
modalRef: HTMLButtonElement | null;
setModalRef: (ref: HTMLButtonElement | null) => void;
+ isOpenCreateTasksWithAIModal: boolean;
+ setIsOpenCreateTasksWithAIModal: (value: boolean) => void;
}
const ModuleTasksContext = createContext(null);
@@ -14,13 +16,17 @@ export const ModuleTasksContextProvider = ({
}) => {
const [modalRef, setModalRef] =
useState(null);
+ const [isOpenCreateTasksWithAIModal, setIsOpenCreateTasksWithAIModal] =
+ useState(false);
const moduleTasksContextValue = useMemo(
() => ({
modalRef,
setModalRef,
+ isOpenCreateTasksWithAIModal,
+ setIsOpenCreateTasksWithAIModal,
}),
- [modalRef, setModalRef]
+ [modalRef, setModalRef, isOpenCreateTasksWithAIModal]
);
return (
diff --git a/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/CreateTaskListsWithAI.tsx b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/CreateTaskListsWithAI.tsx
deleted file mode 100644
index ad112900d..000000000
--- a/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/CreateTaskListsWithAI.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import {
- Button,
- Textarea,
- FormField,
- Label,
- Message,
- Input,
- AccordionNew,
- LG,
-} from '@appquality/unguess-design-system';
-import { useEffect, useMemo, useState } from 'react';
-import { useParams } from 'react-router-dom';
-import { appTheme } from 'src/app/theme';
-import {
- useGetServicesApiKJobsByJobIdQuery,
- usePostServicesApiKUsecasesMutation,
-} from 'src/features/api';
-
-export const CreateTaskListsWithAI = () => {
- const { planId } = useParams();
- const MIN_LENGTH = 1;
- const [userPrompt, setUserPrompt] = useState('');
- const [taskCount, setTaskCount] = useState(3);
- const [isCreating, setIsCreating] = useState(false);
- const [pollingInterval, setPollingInterval] = useState(0);
- const [postServicesApiKUsecases, { data: jobData, error: postError }] =
- usePostServicesApiKUsecasesMutation();
-
- const { data: useCasesData, error: useCasesError } =
- useGetServicesApiKJobsByJobIdQuery(
- { jobId: jobData?.jobId || '' },
- {
- skip: !jobData?.jobId,
- pollingInterval, // number in ms or 0 for no polling
- }
- );
-
- const isFormDisabled = useMemo(
- () => isCreating || pollingInterval > 0,
- [isCreating, pollingInterval]
- );
-
- const isButtonDisabled = useMemo(
- () => userPrompt.length < MIN_LENGTH || isFormDisabled,
- [userPrompt, isFormDisabled]
- );
-
- const handleClick = async () => {
- setIsCreating(true);
- await postServicesApiKUsecases({
- body: {
- planId: planId || '',
- count: taskCount,
- requirements: userPrompt,
- },
- });
- setIsCreating(false);
- };
-
- // Polling for job status every 5 seconds when jobId becomes available or changes
- useEffect(() => {
- if (jobData?.jobId) {
- setPollingInterval(5000);
- }
- }, [jobData?.jobId]);
-
- // Stop polling when job is completed
- useEffect(() => {
- if (useCasesData?.status === 'completed' && useCasesData?.result) {
- setPollingInterval(0);
- }
- }, [useCasesData]);
-
- // Stopping polling when there is an error
- useEffect(() => {
- if (useCasesError) {
- setPollingInterval(0);
- }
- }, [useCasesError]);
-
- return (
-
-
-
-
-
-
-
- setTaskCount(Number(e.currentTarget.value))}
- disabled={isFormDisabled}
- />
-
-
- {postError && (
- Error submitting task list
- )}
- {useCasesError && (
- Error fetching task list results
- )}
-
- {useCasesData?.result && (
-
-
- Generated Task Lists:
-
- {useCasesData.result.useCases.map((useCase: any) => (
-
-
-
-
-
-
-
-
-
-
- ))}
-
- )}
-
- );
-};
diff --git a/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/TasksList.tsx b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/TasksList.tsx
index e579b2fb1..001634a56 100644
--- a/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/TasksList.tsx
+++ b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/TasksList.tsx
@@ -5,23 +5,22 @@ import {
Message,
Span,
} from '@appquality/unguess-design-system';
-import { useMemo, useState } from 'react';
+import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { appTheme } from 'src/app/theme';
import { FEATURE_FLAG_CHANGE_MODULES_VARIANTS } from 'src/constants';
-import { useGetServicesApiKHealthQuery } from 'src/features/api';
import { useModule } from 'src/features/modules/useModule';
import { useModuleConfiguration } from 'src/features/modules/useModuleConfiguration';
import { useFeatureFlag } from 'src/hooks/useFeatureFlag';
import useWindowSize from 'src/hooks/useWindowSize';
-import { useCanShowAiChat } from 'src/pages/Dashboard/hooks/useCanShowAiChat';
import { DeleteModuleConfirmationModal } from 'src/pages/Plan/modules/modal/DeleteModuleConfirmationModal';
import styled from 'styled-components';
import { useIconWithValidation } from '../../useIcon';
+import { useModuleTasksContext } from '../context';
import { useModuleTasks } from '../hooks';
import { AddTaskButton } from './AddTaskButton';
-import { CreateTaskListsWithAI } from './CreateTaskListsWithAI';
import { TasksModal } from './modal';
+import { CreateTaskListsWithAI } from './modal/CreateTaskListsWithAI';
import { TasksContainerAnimation } from './TasksContainerAnimation';
const StyledCard = styled(ContainerCard)`
@@ -54,22 +53,13 @@ const TasksList = () => {
const { getPlanStatus } = useModuleConfiguration();
const { t } = useTranslation();
const { hasFeatureFlag } = useFeatureFlag();
- const { data: apiK_HealthResponse } = useGetServicesApiKHealthQuery();
+ const { isOpenCreateTasksWithAIModal } = useModuleTasksContext();
const [isOpenDeleteModal, setIsOpenDeleteModal] = useState(false);
const Icon = useIconWithValidation();
const { width } = useWindowSize();
const breakpointSm = parseInt(appTheme.breakpoints.sm, 10);
const isMobile = width < breakpointSm;
- const canShowChat = useCanShowAiChat();
- const canShowAiFeatures = useMemo(
- () =>
- canShowChat &&
- apiK_HealthResponse?.success &&
- apiK_HealthResponse?.status === 'healthy',
- [apiK_HealthResponse, canShowChat]
- );
-
const handleDelete = () => {
setIsOpenDeleteModal(true);
};
@@ -113,7 +103,6 @@ const TasksList = () => {
{t('__PLAN_PAGE_MODULE_TASKS_SUBTITLE')}
*
- {!!canShowAiFeatures && }
{error &&
(errorEmpty ? (
{
onConfirm={remove}
/>
)}
+ {isOpenCreateTasksWithAIModal && }
>
);
};
diff --git a/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/AiGeneratorSection.tsx b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/AiGeneratorSection.tsx
new file mode 100644
index 000000000..57491847f
--- /dev/null
+++ b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/AiGeneratorSection.tsx
@@ -0,0 +1,46 @@
+import { Button, MD } from '@appquality/unguess-design-system';
+import { Trans, useTranslation } from 'react-i18next';
+import { appTheme } from 'src/app/theme';
+import { ReactComponent as AiIcon } from 'src/assets/icons/ai-icon.svg';
+
+type AiGeneratorSectionProps = {
+ canShowAiFeatures?: boolean;
+ onOpenCreateWithAI: () => void;
+};
+
+const AiGeneratorSection = ({
+ canShowAiFeatures,
+ onOpenCreateWithAI,
+}: AiGeneratorSectionProps) => {
+ const { t } = useTranslation();
+
+ if (!canShowAiFeatures) {
+ return null;
+ }
+
+ return (
+
+
+ ,
+ }}
+ />
+
+
+
+ );
+};
+
+export { AiGeneratorSection };
diff --git a/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/CreateTaskListsWithAI.tsx b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/CreateTaskListsWithAI.tsx
new file mode 100644
index 000000000..c7f9732f9
--- /dev/null
+++ b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/CreateTaskListsWithAI.tsx
@@ -0,0 +1,343 @@
+import {
+ Alert,
+ Button,
+ FooterItem,
+ FormField,
+ Label,
+ MD,
+ Message,
+ Modal,
+ ModalClose,
+ Notification,
+ Select,
+ Span,
+ Textarea,
+ useToast,
+} from '@appquality/unguess-design-system';
+import { useEffect, useState } from 'react';
+import { Trans, useTranslation } from 'react-i18next';
+import { useParams } from 'react-router-dom';
+import { useAppSelector } from 'src/app/hooks';
+import { appTheme } from 'src/app/theme';
+import {
+ useGetServicesApiKJobsByJobIdQuery,
+ usePostServicesApiKUsecasesMutation,
+} from 'src/features/api';
+import { useModuleTasksContext } from '../../context';
+import { useModuleTasks } from '../../hooks';
+import { processItemOutput } from '../processItemOutput';
+import { LoadingSpinner } from './LoadingSpinner';
+
+// constants
+const MODULES_TO_PROMPT = [
+ 'title',
+ 'goal',
+ 'out_of_scope',
+ 'tasks',
+ 'language',
+ 'touchpoints',
+];
+const MAX_PROMPT_LENGTH = 102300;
+
+const CreateTaskListsWithAI = () => {
+ const { setOutput, value: currentTasks } = useModuleTasks();
+ const { addToast } = useToast();
+ const { planId } = useParams();
+ const { t } = useTranslation();
+ const { setIsOpenCreateTasksWithAIModal } = useModuleTasksContext();
+ const MIN_LENGTH = 1;
+ const [userPrompt, setUserPrompt] = useState('');
+ const [usecaseNumber, setUsecaseNumber] = useState(
+ undefined
+ );
+ const [pollingInterval, setPollingInterval] = useState(0);
+ const [isOpenConfirmation, setIsOpenConfirmation] = useState(false);
+
+ const { records } = useAppSelector((state) => state.planModules);
+
+ // API hooks
+ const [
+ postServicesApiKUsecases,
+ { data: jobData, error: postError, isLoading: isPostingRequest },
+ ] = usePostServicesApiKUsecasesMutation();
+ const { data: useCasesData, error: useCasesError } =
+ useGetServicesApiKJobsByJobIdQuery(
+ { jobId: jobData?.jobId || '' },
+ {
+ skip: !jobData?.jobId,
+ pollingInterval, // number in ms or 0 for no polling
+ }
+ );
+
+ const handleClose = () => {
+ if (isPostingRequest || pollingInterval > 0) {
+ setIsOpenConfirmation(true);
+ } else {
+ setIsOpenCreateTasksWithAIModal(false);
+ }
+ };
+
+ const handleConfirmClose = () => {
+ setIsOpenConfirmation(false);
+ setIsOpenCreateTasksWithAIModal(false);
+ };
+ const handleCancelClose = () => {
+ setIsOpenConfirmation(false);
+ };
+
+ const handleStop = () => {
+ setPollingInterval(0);
+ };
+
+ const handleClick = async () => {
+ // gather modules info to prepend to the user prompt
+ const modulesInfo = Object.entries(records)
+ .filter(([key]) => MODULES_TO_PROMPT.includes(key))
+ .map(
+ ([key, item]) =>
+ `Module: ${key}, Config: ${JSON.stringify(processItemOutput(item))}`
+ )
+ .join('\n');
+ const fullPrompt = `User prompt:\n${userPrompt}\nModules info:\n${modulesInfo}`;
+
+ await postServicesApiKUsecases({
+ body: {
+ planId: planId || '',
+ count: usecaseNumber as number, // usecaseNumber always have a value here because the button is disabled when it's undefined
+ requirements: fullPrompt.slice(0, MAX_PROMPT_LENGTH),
+ },
+ });
+ };
+
+ // Polling for job status every 5 seconds when jobId becomes available or changes
+ useEffect(() => {
+ if (jobData?.jobId) {
+ setPollingInterval(5000);
+ }
+ }, [jobData?.jobId]);
+
+ // Stop polling when job is completed and process the results
+ useEffect(() => {
+ if (useCasesData?.status === 'completed' && useCasesData?.result) {
+ setPollingInterval(0);
+ if (useCasesData?.result) {
+ const newTasks = useCasesData.result.useCases.map((useCase) => ({
+ kind: 'bug' as const,
+ title: useCase.title,
+ description: useCase.mainFlow,
+ id: useCase.id,
+ }));
+ setOutput([...currentTasks, ...newTasks]);
+ }
+ // show a success message
+ addToast(
+ ({ close }) => (
+
+ ),
+ { placement: 'top' }
+ );
+ // close the modal after processing the result
+ setIsOpenCreateTasksWithAIModal(false);
+ }
+ }, [useCasesData]);
+
+ // Stopping polling when there is an error
+ useEffect(() => {
+ if (useCasesError) {
+ setPollingInterval(0);
+ }
+ }, [useCasesError]);
+
+ let buttonLabel: string;
+ if (isPostingRequest) {
+ buttonLabel = t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CREATING',
+ 'Creating...'
+ );
+ } else if (pollingInterval > 0) {
+ buttonLabel = t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_STOP',
+ 'Stop Generation'
+ );
+ } else {
+ buttonLabel = t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CREATE_BUTTON',
+ 'Create'
+ );
+ }
+
+ return (
+
+
+ {t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_MODAL_HEADER'
+ )}
+
+
+
+
+ ,
+ bold: ,
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ {t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ALERT_TITLE'
+ )}
+
+
+ ),
+ li: ,
+ }}
+ />
+
+ {postError && (
+
+ {t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ERROR_SUBMITTING'
+ )}
+
+ )}
+ {useCasesError && (
+
+ {t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_ERROR_FETCHING'
+ )}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {isOpenConfirmation && (
+
+
+ {t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_HEADER'
+ )}
+
+
+ {t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_CONFIRM_CLOSE_BODY'
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export { CreateTaskListsWithAI };
diff --git a/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/LoadingSpinner.tsx b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/LoadingSpinner.tsx
new file mode 100644
index 000000000..e265c6dcb
--- /dev/null
+++ b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/LoadingSpinner.tsx
@@ -0,0 +1,184 @@
+import { Paragraph, Spinner } from '@appquality/unguess-design-system';
+import { useEffect, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { appTheme } from 'src/app/theme';
+import { styled } from 'styled-components';
+
+const Loading = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0.75;
+ background-color: ${({ theme }) => theme.palette.white};
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ gap: ${appTheme.space.md};
+ font-size: ${appTheme.fontSizes.md};
+ padding: 0 ${appTheme.space.lg};
+`;
+
+const TasksList = styled.ul`
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: ${appTheme.space.xxs};
+`;
+
+const TaskItem = styled.li<{ $isActive: boolean; $isCompleted: boolean }>`
+ position: relative;
+ padding-left: ${appTheme.space.lg};
+ opacity: ${({ $isActive, $isCompleted }) =>
+ $isActive || $isCompleted ? 1 : 0.5};
+ ${({ $isActive, $isCompleted }) =>
+ !$isActive && !$isCompleted
+ ? `animation: pulse 1.5s ease-in-out infinite;`
+ : ''}
+ transition: opacity 0.3s ease-in-out;
+
+ &::before {
+ content: '○';
+ position: absolute;
+ left: 0;
+ ${({ $isActive, $isCompleted }) =>
+ !$isActive && !$isCompleted
+ ? `animation: pulse 1.5s ease-in-out infinite;`
+ : ''}
+ }
+
+ ${({ $isCompleted }) =>
+ $isCompleted &&
+ `
+ &::before {
+ content: '✓';
+ font-weight: bold;
+ animation: checkmark .4s ease-in-out;
+ }
+ `}
+
+ @keyframes checkmark {
+ 0% {
+ opacity: 0;
+ transform: scale(0.5);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+
+ @keyframes pulse {
+ 0%,
+ 100% {
+ opacity: 0.4;
+ }
+ 50% {
+ opacity: 0.8;
+ }
+ }
+`;
+
+const PHASES = {
+ GATHERING: { start: 0, end: 5000 },
+ PROCESSING: { start: 5000, end: 30000 },
+ FINALIZING: { start: 30000, end: Infinity },
+};
+
+const LoadingSpinner = () => {
+ const { t } = useTranslation();
+ const [elapsedTime, setElapsedTime] = useState(0);
+
+ const PROCESSING_TASKS = {
+ 1: t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_COLLECTING_INPUTS'
+ ),
+ 2: t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_ANALYZING_CONTEXT'
+ ),
+ 3: t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_IDENTIFYING_AREAS'
+ ),
+ 4: t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_TASK_GENERATING_TASKS'
+ ),
+ };
+
+ // Calculate which task is active based on elapsed time
+ const getActiveTaskIndex = () => {
+ const processingDuration = PHASES.PROCESSING.end - PHASES.PROCESSING.start;
+ const timeInProcessing = elapsedTime - PHASES.PROCESSING.start;
+ if (timeInProcessing < 0) return -1;
+ return Math.floor(
+ (timeInProcessing / processingDuration) *
+ Object.keys(PROCESSING_TASKS).length
+ );
+ };
+
+ const activeTaskIndex = getActiveTaskIndex();
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setElapsedTime((prev) => prev + 1000);
+ }, 1000);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ const getLoadingContent = () => {
+ if (elapsedTime < PHASES.GATHERING.end) {
+ return (
+
+ {t(
+ '__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_GATHERING'
+ )}
+
+ );
+ }
+
+ if (
+ elapsedTime >= PHASES.PROCESSING.start &&
+ elapsedTime < PHASES.PROCESSING.end
+ ) {
+ return (
+
+ {Object.entries(PROCESSING_TASKS).map(([, taskLabel], idx) => {
+ const isCompleted = idx < activeTaskIndex;
+ const isActive = idx === activeTaskIndex;
+
+ return (
+
+ {taskLabel}
+
+ );
+ })}
+
+ );
+ }
+
+ return (
+
+ {t('__PLAN_PAGE_MODULE_TASKS_ADD_TASK_MODAL_CREATE_WITH_AI_FINALIZING')}
+
+ );
+ };
+
+ return (
+
+
+ {getLoadingContent()}
+
+ );
+};
+
+export { LoadingSpinner };
diff --git a/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/TasksModal.tsx b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/TasksModal.tsx
index b53f4b25a..0be4dcc1b 100644
--- a/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/TasksModal.tsx
+++ b/src/pages/Plan/modules/Factory/modules/Tasks/Component/parts/modal/TasksModal.tsx
@@ -1,14 +1,18 @@
import { MD, Tabs, TooltipModal } from '@appquality/unguess-design-system';
+import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { appTheme } from 'src/app/theme';
import { Divider } from 'src/common/components/divider';
import { components } from 'src/common/schema';
import { FEATURE_FLAG_CHANGE_MODULES_VARIANTS } from 'src/constants';
+import { useGetServicesApiKHealthQuery } from 'src/features/api';
import { useFeatureFlag } from 'src/hooks/useFeatureFlag';
+import { useCanShowAiChat } from 'src/pages/Dashboard/hooks/useCanShowAiChat';
import styled from 'styled-components';
import { useModuleTasksContext } from '../../context';
import { useModuleTasks } from '../../hooks';
import { AccessibilityTasks } from './AccessibilityTasks';
+import { AiGeneratorSection } from './AiGeneratorSection';
import { ExperientialTasks } from './ExperientialTasks';
import { FunctionalTasks } from './FunctionalTasks';
import { SurveyTasks } from './SurveyTasks';
@@ -20,8 +24,18 @@ const StyledTabs = styled(Tabs)`
const TasksModal = () => {
const { t } = useTranslation();
const { variant, setVariant } = useModuleTasks();
- const { modalRef, setModalRef } = useModuleTasksContext();
+ const { modalRef, setModalRef, setIsOpenCreateTasksWithAIModal } =
+ useModuleTasksContext();
const { hasFeatureFlag } = useFeatureFlag();
+ const { data: apiK_HealthResponse } = useGetServicesApiKHealthQuery();
+ const canShowChat = useCanShowAiChat();
+ const canShowAiFeatures = useMemo(
+ () =>
+ canShowChat &&
+ apiK_HealthResponse?.success &&
+ apiK_HealthResponse?.status === 'healthy',
+ [apiK_HealthResponse, canShowChat]
+ );
const variants = [
'default',
'functional',
@@ -70,14 +84,21 @@ const TasksModal = () => {
+ setIsOpenCreateTasksWithAIModal(true)}
+ />
-
+ setIsOpenCreateTasksWithAIModal(true)}
+ />
{
+ if (Array.isArray(item)) {
+ // If it's an array, process each item in the array
+ return item.map((subItem) => processItemOutput(subItem));
+ }
+ if (typeof item === 'object' && item !== null) {
+ // recursively process nested values
+ return Object.fromEntries(
+ Object.entries(item)
+ // here we can filter out any keys we don't want to include in the prompt, for example 'id'
+ .filter(([key]) => key !== 'id')
+ .map(([key, value]) => [key, processItemOutput(value)])
+ );
+ }
+ return item;
+};