Fix/descriptive ai feat (#464)
* fix AI feature not working * fixup! fix AI feature not working
This commit is contained in:
parent
81e4ec7ce1
commit
00eda4b936
6 changed files with 401 additions and 8 deletions
|
|
@ -115,6 +115,122 @@ describe('Users Routes', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Task Intelligence Settings', () => {
|
||||
describe('GET /api/profile - task_intelligence_enabled field', () => {
|
||||
it('should return task_intelligence_enabled in profile', async () => {
|
||||
const response = await agent.get('/api/profile');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toHaveProperty(
|
||||
'task_intelligence_enabled'
|
||||
);
|
||||
expect(typeof response.body.task_intelligence_enabled).toBe(
|
||||
'boolean'
|
||||
);
|
||||
});
|
||||
|
||||
it('should default to true for new users', async () => {
|
||||
const response = await agent.get('/api/profile');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
// New users should have task intelligence enabled by default
|
||||
expect(response.body.task_intelligence_enabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /api/profile - disable task intelligence', () => {
|
||||
it('should disable task intelligence (AI popups)', async () => {
|
||||
const response = await agent
|
||||
.patch('/api/profile')
|
||||
.send({ task_intelligence_enabled: false });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.task_intelligence_enabled).toBe(false);
|
||||
|
||||
// Verify it persisted
|
||||
const getResponse = await agent.get('/api/profile');
|
||||
expect(getResponse.body.task_intelligence_enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('should enable task intelligence (AI popups)', async () => {
|
||||
// First disable it
|
||||
await user.update({ task_intelligence_enabled: false });
|
||||
|
||||
// Then enable it via API
|
||||
const response = await agent
|
||||
.patch('/api/profile')
|
||||
.send({ task_intelligence_enabled: true });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.task_intelligence_enabled).toBe(true);
|
||||
|
||||
// Verify it persisted
|
||||
const getResponse = await agent.get('/api/profile');
|
||||
expect(getResponse.body.task_intelligence_enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow updating task intelligence with other fields', async () => {
|
||||
const response = await agent.patch('/api/profile').send({
|
||||
task_intelligence_enabled: false,
|
||||
appearance: 'dark',
|
||||
language: 'es',
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.task_intelligence_enabled).toBe(false);
|
||||
expect(response.body.appearance).toBe('dark');
|
||||
expect(response.body.language).toBe('es');
|
||||
});
|
||||
|
||||
it('should handle boolean conversion from string', async () => {
|
||||
// Test that "false" string gets converted to boolean false
|
||||
const response = await agent
|
||||
.patch('/api/profile')
|
||||
.send({ task_intelligence_enabled: 'false' });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
// Should handle string to boolean conversion
|
||||
expect([false, 'false']).toContain(
|
||||
response.body.task_intelligence_enabled
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Task Intelligence Feature Verification', () => {
|
||||
it('should respect disabled setting - no AI suggestions shown', async () => {
|
||||
// Disable task intelligence
|
||||
await agent
|
||||
.patch('/api/profile')
|
||||
.send({ task_intelligence_enabled: false });
|
||||
|
||||
// Verify the setting is stored correctly
|
||||
const response = await agent.get('/api/profile');
|
||||
expect(response.body.task_intelligence_enabled).toBe(false);
|
||||
|
||||
// This verifies that the backend correctly stores the setting
|
||||
// Frontend tests verify that popups are actually hidden
|
||||
});
|
||||
|
||||
it('should persist across sessions', async () => {
|
||||
// Disable task intelligence
|
||||
await agent
|
||||
.patch('/api/profile')
|
||||
.send({ task_intelligence_enabled: false });
|
||||
|
||||
// Simulate new session by creating new agent
|
||||
const newAgent = request.agent(app);
|
||||
await newAgent.post('/api/login').send({
|
||||
email: user.email,
|
||||
password: 'password123',
|
||||
});
|
||||
|
||||
// Verify setting persisted
|
||||
const response = await newAgent.get('/api/profile');
|
||||
expect(response.body.task_intelligence_enabled).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/profile/task-summary/toggle', () => {
|
||||
beforeEach(async () => {
|
||||
await user.update({ task_summary_enabled: false });
|
||||
|
|
|
|||
|
|
@ -1710,7 +1710,7 @@ const ProfileSettings: React.FC<ProfileSettingsProps> = ({
|
|||
<p>
|
||||
{t(
|
||||
'profile.taskIntelligenceDescription',
|
||||
'Get helpful suggestions to make your task names more descriptive and actionable.'
|
||||
'Show popup alerts while typing task names that suggest improvements like "Make it more descriptive!", "Be more specific!", or "Add an action verb!". Disable this if you prefer typing in your own shorthand without suggestions.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const NewTask: React.FC<NewTaskProps> = ({ onTaskCreate }) => {
|
|||
const [taskName, setTaskName] = useState<string>('');
|
||||
const [showNameLengthHelper, setShowNameLengthHelper] = useState(false);
|
||||
const [taskIntelligenceEnabled, setTaskIntelligenceEnabled] =
|
||||
useState(true);
|
||||
useState(false);
|
||||
const { showErrorToast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
@ -35,10 +35,10 @@ const NewTask: React.FC<NewTaskProps> = ({ onTaskCreate }) => {
|
|||
JSON.stringify(enabled)
|
||||
);
|
||||
} catch {
|
||||
setTaskIntelligenceEnabled(true); // Default to enabled
|
||||
setTaskIntelligenceEnabled(false); // Default to disabled on error
|
||||
localStorage.setItem(
|
||||
'taskIntelligenceEnabled',
|
||||
JSON.stringify(true)
|
||||
JSON.stringify(false)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,258 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import TaskTitleSection from '../TaskTitleSection';
|
||||
|
||||
// Mock react-i18next
|
||||
jest.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, defaultValue: string) => defaultValue,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('TaskTitleSection - Task Intelligence Settings', () => {
|
||||
const mockOnChange = jest.fn();
|
||||
const mockOnSubmit = jest.fn();
|
||||
|
||||
const defaultProps = {
|
||||
taskId: 1,
|
||||
value: '',
|
||||
onChange: mockOnChange,
|
||||
taskAnalysis: null,
|
||||
taskIntelligenceEnabled: false,
|
||||
onSubmit: mockOnSubmit,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('When task intelligence is DISABLED', () => {
|
||||
it('should NOT show AI suggestions even when task is vague', () => {
|
||||
const vagueTaskAnalysis = {
|
||||
isVague: true,
|
||||
severity: 'high' as const,
|
||||
reason: 'short',
|
||||
suggestion: 'Try adding more details',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskTitleSection
|
||||
{...defaultProps}
|
||||
taskIntelligenceEnabled={false}
|
||||
taskAnalysis={vagueTaskAnalysis}
|
||||
value="test"
|
||||
/>
|
||||
);
|
||||
|
||||
// AI suggestion messages should NOT be in the document
|
||||
expect(
|
||||
screen.queryByText('Make it more descriptive!')
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('Be more specific!')
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('Add an action verb!')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT show AI suggestions for short task names', () => {
|
||||
const shortTaskAnalysis = {
|
||||
isVague: true,
|
||||
severity: 'medium' as const,
|
||||
reason: 'short',
|
||||
suggestion:
|
||||
'Try adding more details like "Call dentist to schedule cleaning appointment" instead of just "Call dentist"',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskTitleSection
|
||||
{...defaultProps}
|
||||
taskIntelligenceEnabled={false}
|
||||
taskAnalysis={shortTaskAnalysis}
|
||||
value="call"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByText('Make it more descriptive!')
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText(/Try adding more details/)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT show AI suggestions for tasks without action verbs', () => {
|
||||
const noVerbAnalysis = {
|
||||
isVague: true,
|
||||
severity: 'low' as const,
|
||||
reason: 'no_verb',
|
||||
suggestion:
|
||||
'What specific action do you need to take? Try starting with a verb.',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskTitleSection
|
||||
{...defaultProps}
|
||||
taskIntelligenceEnabled={false}
|
||||
taskAnalysis={noVerbAnalysis}
|
||||
value="dentist appointment"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByText('Add an action verb!')
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText(/What specific action/)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT show AI suggestions for vague patterns', () => {
|
||||
const vaguePatternAnalysis = {
|
||||
isVague: true,
|
||||
severity: 'medium' as const,
|
||||
reason: 'vague_pattern',
|
||||
suggestion: 'Try to be more specific',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskTitleSection
|
||||
{...defaultProps}
|
||||
taskIntelligenceEnabled={false}
|
||||
taskAnalysis={vaguePatternAnalysis}
|
||||
value="check on things"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByText('Be more specific!')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When task intelligence is ENABLED', () => {
|
||||
it('should show "Make it more descriptive!" for short task names', () => {
|
||||
const shortTaskAnalysis = {
|
||||
isVague: true,
|
||||
severity: 'medium' as const,
|
||||
reason: 'short',
|
||||
suggestion:
|
||||
'Try adding more details like "Call dentist to schedule cleaning appointment" instead of just "Call dentist"',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskTitleSection
|
||||
{...defaultProps}
|
||||
taskIntelligenceEnabled={true}
|
||||
taskAnalysis={shortTaskAnalysis}
|
||||
value="call"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText('Make it more descriptive!')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show "Add an action verb!" for tasks without verbs', () => {
|
||||
const noVerbAnalysis = {
|
||||
isVague: true,
|
||||
severity: 'low' as const,
|
||||
reason: 'no_verb',
|
||||
suggestion:
|
||||
'What specific action do you need to take? Try starting with a verb.',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskTitleSection
|
||||
{...defaultProps}
|
||||
taskIntelligenceEnabled={true}
|
||||
taskAnalysis={noVerbAnalysis}
|
||||
value="dentist appointment"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Add an action verb!')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show "Be more specific!" for vague patterns', () => {
|
||||
const vaguePatternAnalysis = {
|
||||
isVague: true,
|
||||
severity: 'medium' as const,
|
||||
reason: 'vague_pattern',
|
||||
suggestion: 'Try to be more specific',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskTitleSection
|
||||
{...defaultProps}
|
||||
taskIntelligenceEnabled={true}
|
||||
taskAnalysis={vaguePatternAnalysis}
|
||||
value="check on things"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Be more specific!')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT show suggestions when task is not vague', () => {
|
||||
const goodTaskAnalysis = {
|
||||
isVague: false,
|
||||
severity: 'low' as const,
|
||||
reason: '',
|
||||
};
|
||||
|
||||
render(
|
||||
<TaskTitleSection
|
||||
{...defaultProps}
|
||||
taskIntelligenceEnabled={true}
|
||||
taskAnalysis={goodTaskAnalysis}
|
||||
value="Call dentist to schedule annual cleaning appointment"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByText('Make it more descriptive!')
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('Be more specific!')
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText('Add an action verb!')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Task input rendering', () => {
|
||||
it('should render task name input with correct value', () => {
|
||||
const { container } = render(
|
||||
<TaskTitleSection {...defaultProps} value="My task name" />
|
||||
);
|
||||
|
||||
const input = container.querySelector('input[name="name"]');
|
||||
expect(input).toBeInTheDocument();
|
||||
expect(input).toHaveValue('My task name');
|
||||
});
|
||||
|
||||
it('should render with placeholder text', () => {
|
||||
const { container } = render(
|
||||
<TaskTitleSection {...defaultProps} value="" />
|
||||
);
|
||||
|
||||
const input = container.querySelector('input[name="name"]');
|
||||
expect(input).toBeInTheDocument();
|
||||
expect(input).toHaveAttribute('placeholder', 'Add Task Name');
|
||||
});
|
||||
|
||||
it('should have required attribute', () => {
|
||||
const { container } = render(
|
||||
<TaskTitleSection {...defaultProps} value="" />
|
||||
);
|
||||
|
||||
const input = container.querySelector('input[name="name"]');
|
||||
expect(input).toHaveAttribute('required');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -11,6 +11,7 @@ import {
|
|||
TaskAnalysis,
|
||||
} from '../../utils/taskIntelligenceService';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getTaskIntelligenceEnabled } from '../../utils/profileService';
|
||||
import {
|
||||
TagIcon,
|
||||
FolderIcon,
|
||||
|
|
@ -78,7 +79,8 @@ const TaskModal: React.FC<TaskModalProps> = ({
|
|||
const [parentTask, setParentTask] = useState<Task | null>(null);
|
||||
const [parentTaskLoading, setParentTaskLoading] = useState(false);
|
||||
const [taskAnalysis, setTaskAnalysis] = useState<TaskAnalysis | null>(null);
|
||||
const [taskIntelligenceEnabled] = useState(true);
|
||||
const [taskIntelligenceEnabled, setTaskIntelligenceEnabled] =
|
||||
useState(false);
|
||||
const [subtasks, setSubtasks] = useState<Task[]>([]);
|
||||
|
||||
// Collapsible section states - subtasks is derived from autoFocusSubtasks
|
||||
|
|
@ -205,8 +207,25 @@ const TaskModal: React.FC<TaskModalProps> = ({
|
|||
fetchParentTask();
|
||||
}, [task.recurring_parent_id, isOpen]);
|
||||
|
||||
// Don't fetch task intelligence setting - use default enabled state
|
||||
// This prevents unnecessary API calls when opening the modal
|
||||
// Fetch task intelligence setting from user profile
|
||||
useEffect(() => {
|
||||
const fetchIntelligenceSetting = async () => {
|
||||
try {
|
||||
const enabled = await getTaskIntelligenceEnabled();
|
||||
setTaskIntelligenceEnabled(enabled);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error fetching task intelligence setting:',
|
||||
error
|
||||
);
|
||||
setTaskIntelligenceEnabled(false); // Default to disabled on error
|
||||
}
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
fetchIntelligenceSetting();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
// Auto-scroll to subtasks section when modal opens with autoFocusSubtasks
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@
|
|||
},
|
||||
"frequencyHelp": "Choose how often you want to receive task summaries",
|
||||
"taskIntelligence": "Task Intelligence",
|
||||
"taskIntelligenceDescription": "Get helpful suggestions to make your task names more descriptive and actionable.",
|
||||
"taskIntelligenceDescription": "Show popup alerts while typing task names that suggest improvements like \"Make it more descriptive!\", \"Be more specific!\", or \"Add an action verb!\". Disable this if you prefer typing in your own shorthand without suggestions.",
|
||||
"enableTaskIntelligence": "Enable Task Intelligence Assistant",
|
||||
"autoSuggestNextActions": "Auto-Suggest Next Actions",
|
||||
"autoSuggestNextActionsDescription": "When creating a project, automatically prompt for the very next physical action to take.",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue