Fix/descriptive ai feat (#464)

* fix AI feature not working

* fixup! fix AI feature not working
This commit is contained in:
Chris 2025-11-02 14:14:55 +02:00 committed by GitHub
parent 81e4ec7ce1
commit 00eda4b936
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 401 additions and 8 deletions

View file

@ -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 });

View file

@ -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>

View file

@ -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)
);
}
};

View file

@ -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');
});
});
});

View file

@ -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(() => {

View file

@ -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.",