Files
twenty/packages/twenty-front/src/modules/ai/components/NavigationDrawerAiChatThreadDateSection.tsx
T
Félix Malfait 6117a1d6c0 refactor: standardize AI acronym to Ai (PascalCase) across internal identifiers (#19837)
## Summary

The "AI" acronym was rendered inconsistently across the codebase. The
backend AI module had settled on PascalCase `Ai` (`AiAgentModule`,
`AiBillingService`, `AiChatModule`, `AiModelRegistryService`, etc.),
while frontend components, several DTOs, a few types, and shared
identifiers still used all-caps `AI` (`AIChatTab`,
`AISystemPromptPreviewDTO`, `SettingsPath.AIPrompts`, ...). CLAUDE.md
specifies PascalCase for classes; this PR normalizes everything internal
to `Ai`.

**This is a pure internal rename.** The GraphQL schema is untouched —
`@ObjectType` decorator string arguments, resolver method names (which
become Query/Mutation field names), gql template contents, and the
`generated-metadata/graphql.ts` file are preserved verbatim. The only
visible change is TypeScript identifiers and file names.

## Also folded in (adjacent cleanups)

- **`AgentModelConfigService` → `AiModelConfigService`**. Lives in
`ai-models/` and is used by multiple AI code paths, not just the Agent
entity. The "Agent" prefix was misleading.
- **`generate-text-input.dto.ts` → `generate-text.input.ts`**. The
`ai-agent/dtos/` folder already uses `<entity>.input.ts` convention for
Input classes (`create-agent.input.ts` etc.); the old path mixed
`.dto.ts` file extension with a class that has no DTO suffix. File
rename only; class stays `GenerateTextInput`.
- **Removed stale TODO** in `ai-model-config.type.ts` that asked for the
`AiModelConfig` rename that this PR performs.

## Rename methodology

Bulk rename via perl with anchored regex
`(?<!['"])(?<![A-Z.])AI([A-Z])(?=[a-z])/Ai$1/g`:

- **Lookbehind for non-uppercase** skips adjacent acronyms (`MOSAIC`,
`OIDCSSO`) and leaves `AIRBNB_ID` alone.
- **Lookbehind for non-quote** protects most string literals.
- **Lookahead for lowercase** restricts matches to PascalCase
identifiers (`AIChatTab`), leaving SCREAMING_SNAKE constants untouched.

Strict file-scope exclusions: `generated-metadata/**`, `generated/**`,
`locales/**`, `migrations/**`, `illustrations/**`, `halftone/**`, and
the two gql template files (`queries/getAISystemPromptPreview.ts`,
`mutations/uploadAIChatFile.ts`).

Post-rename reverts for identifiers where the regex was too eager:
- Backend resolver method names kept: `getAISystemPromptPreview`,
`uploadAIChatFile` (they are GraphQL field names).
- `@ObjectType('AdminAIModels')` / `('AISystemPromptPreview')` /
`('AISystemPromptSection')` kept as-is.
- Backend classes `ClientAIModelConfig` / `AdminAIModelConfig` kept
as-is (they use `@ObjectType()` with no argument, so the class name IS
the schema name).
- External-library symbols restored: `OpenAIProvider`,
`createOpenAICompatible`, `vercelAIIntegration`.

File renames use a two-step rename to work on macOS case-insensitive
filesystems: `git mv X.tsx X.tsx.tmp && git mv X.tsx.tmp renamed.tsx`.

## Diff audit

- 0 changes to migrations
- 0 changes to locale `.po` / `.ts` files
- 0 changes to `generated-metadata/graphql.ts`
- 0 changes to website illustration files (base64 blobs preserved)
- 0 renames inside user-facing translation strings (`t\`…\``,
`msg\`…\``, `<Trans>…</Trans>`)

## Test plan

- [x] `npx nx typecheck twenty-server` — PASS
- [x] `npx nx typecheck twenty-front` — PASS
- [x] `npx jest ai-model admin agent-role` — 79/79 PASS
- [x] `npx oxlint --type-aware` on 118 changed files — 0 errors
- [x] `npx prettier --check` on 118 changed files — clean
- [ ] CI
2026-04-19 13:29:35 +02:00

77 lines
2.4 KiB
TypeScript

import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { IconComment } from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { type AgentChatThread } from '~/generated-metadata/graphql';
import { beautifyPastDateRelativeToNowShort } from '~/utils/date-utils';
const StyledDateSection = styled.section`
margin-bottom: ${themeCssVariables.spacing[4]};
`;
const StyledThreadList = styled.div`
display: flex;
flex-direction: column;
gap: ${themeCssVariables.spacing['0.5']};
`;
const StyledDateHeader = styled.div`
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.xs};
font-weight: ${themeCssVariables.font.weight.medium};
margin-bottom: ${themeCssVariables.spacing[1]};
padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
`;
const StyledThreadTimestamp = styled.span`
color: ${themeCssVariables.font.color.light};
font-size: ${themeCssVariables.font.size.xs};
font-weight: ${themeCssVariables.font.weight.regular};
padding-right: ${themeCssVariables.spacing['0.5']};
`;
export type NavigationDrawerAiChatThreadDateSectionProps = {
title: string;
threads: AgentChatThread[];
currentThreadId: string | null;
onThreadClick: (thread: AgentChatThread) => void;
};
export const NavigationDrawerAiChatThreadDateSection = ({
title,
threads,
currentThreadId,
onThreadClick,
}: NavigationDrawerAiChatThreadDateSectionProps) => {
const { t } = useLingui();
return (
<StyledDateSection>
<StyledDateHeader>{title}</StyledDateHeader>
<StyledThreadList>
{threads.map((thread) => {
const isActive = currentThreadId === thread.id;
const timestamp = beautifyPastDateRelativeToNowShort(
thread.updatedAt ?? thread.createdAt,
);
return (
<NavigationDrawerItem
key={thread.id}
label={thread.title || t`New chat`}
Icon={IconComment}
active={isActive}
onClick={() => onThreadClick(thread)}
alwaysShowRightOptions
rightOptions={
<StyledThreadTimestamp>{timestamp}</StyledThreadTimestamp>
}
/>
);
})}
</StyledThreadList>
</StyledDateSection>
);
};