twenty/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx
Félix Malfait 3216b634a3
feat: improve AI chat - system prompt, tool output, context window display (#17769)
⚠️ **AI-generated PR — not ready for review** ⚠️

cc @FelixMalfait

---

## Changes

### System prompt improvements
- Explicit skill-before-tools workflow to prevent the model from calling
tools without loading the matching skill first
- Data efficiency guidance (default small limits, use filters)
- Pluralized `load_skill` → `load_skills` for consistency with
`load_tools`

### Token usage reduction
- Output serialization layer: strips null/undefined/empty values from
tool results
- Lowered default `find_*` limit from 100 → 10, max from 1000 → 100

### System object tool generation
- System objects (calendar events, messages, etc.) now generate AI tools
- Only workflow-related and favorite-related objects are excluded

### Context window display fix
- **Bug**: UI compared cumulative tokens (sum of all turns) against
single-request context window → showed 100% after a few turns
- **Fix**: Track `conversationSize` (last step's `inputTokens`) which
represents the actual conversation history size sent to the model
- New `conversationSize` column on thread entity with migration

### Workspace AI instructions
- Support for custom workspace-level AI instructions

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
2026-02-09 14:26:02 +01:00

110 lines
3.6 KiB
TypeScript

import styled from '@emotion/styled';
import { Link } from 'react-router-dom';
import { SettingsOptionCardContentButton } from '@/settings/components/SettingsOptions/SettingsOptionCardContentButton';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { t } from '@lingui/core/macro';
import {
H2Title,
IconFileText,
IconSettings,
IconSparkles,
IconTool,
} from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Card, Section } from 'twenty-ui/layout';
import { SettingsAIMCP } from './components/SettingsAIMCP';
import { SettingsAIRouterSettings } from './components/SettingsAIRouterSettings';
import { SettingsSkillsTable } from './components/SettingsSkillsTable';
import { SettingsToolsTable } from './components/SettingsToolsTable';
import { SETTINGS_AI_TABS } from './constants/SettingsAiTabs';
const StyledLink = styled(Link)`
text-decoration: none;
`;
export const SettingsAI = () => {
const activeTabId = useRecoilComponentValue(
activeTabIdComponentState,
SETTINGS_AI_TABS.COMPONENT_INSTANCE_ID,
);
const tabs = [
{
id: SETTINGS_AI_TABS.TABS_IDS.SKILLS,
title: t`Skills`,
Icon: IconSparkles,
},
{
id: SETTINGS_AI_TABS.TABS_IDS.TOOLS,
title: t`Tools`,
Icon: IconTool,
},
{
id: SETTINGS_AI_TABS.TABS_IDS.SETTINGS,
title: t`Settings`,
Icon: IconSettings,
},
];
const isSkillsTab = activeTabId === SETTINGS_AI_TABS.TABS_IDS.SKILLS;
const isToolsTab = activeTabId === SETTINGS_AI_TABS.TABS_IDS.TOOLS;
const isSettingsTab = activeTabId === SETTINGS_AI_TABS.TABS_IDS.SETTINGS;
return (
<SubMenuTopBarContainer
title={t`AI`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{ children: t`AI` },
]}
>
<SettingsPageContainer>
<TabList
tabs={tabs}
componentInstanceId={SETTINGS_AI_TABS.COMPONENT_INSTANCE_ID}
/>
{isSkillsTab && <SettingsSkillsTable />}
{isToolsTab && <SettingsToolsTable />}
{isSettingsTab && (
<>
<SettingsAIRouterSettings />
<Section>
<H2Title
title={t`System Prompt`}
description={t`View and customize AI instructions`}
/>
<Card rounded>
<SettingsOptionCardContentButton
Icon={IconFileText}
title={t`System Prompt`}
description={t`View the AI system prompt and add custom instructions`}
Button={
<StyledLink to={getSettingsPath(SettingsPath.AIPrompts)}>
<Button
title={t`Configure`}
variant="secondary"
size="small"
/>
</StyledLink>
}
/>
</Card>
</Section>
<SettingsAIMCP />
</>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};