feat(gitea): add Gitea/Forgejo as a git provider

Full parity with the existing GitLab provider:
- Server module packages/web/server/lib/gitea (auth/client/repo/routes + docs + tests)
  with Gitea REST v1 API, PAT + base URL auth, multi-account storage
- Shared GiteaAPI types and web API client
- Provider detection generalized with user-configurable custom domains
  per provider (github/gitlab/gitea), additive with built-in defaults
  (github.com, gitlab.com) and connected-account hosts; precedence
  github -> gitlab -> gitea
- Gitea PR view, issues section, pickers, integration dialog, branch
  PR status helper, settings UI (PAT + base URL + custom domains)
- Magic prompts (gitea.pr.review, gitea.issue.review) and full 11-locale
  i18n parity
This commit is contained in:
2026-08-16 16:27:49 +00:00
parent be13272eb1
commit ca91fd7e2d
65 changed files with 10667 additions and 101 deletions
+29 -4
View File
@@ -63,6 +63,8 @@ import { GitHubIssuePickerDialog } from '@/components/session/GitHubIssuePickerD
import { GitHubPrPickerDialog } from '@/components/session/GitHubPrPickerDialog';
import { GitLabIssuePickerDialog } from '@/components/session/GitLabIssuePickerDialog';
import { GitLabMrPickerDialog } from '@/components/session/GitLabMrPickerDialog';
import { GiteaIssuePickerDialog } from '@/components/session/GiteaIssuePickerDialog';
import { GiteaPrPickerDialog } from '@/components/session/GiteaPrPickerDialog';
import { useGitProvider } from '@/lib/gitProvider';
import { Icon } from "@/components/icon/Icon";
import { DraftPresetChips } from './DraftPresetChips';
@@ -669,6 +671,8 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
const [prPickerOpen, setPrPickerOpen] = React.useState(false);
const [gitlabIssuePickerOpen, setGitlabIssuePickerOpen] = React.useState(false);
const [gitlabMrPickerOpen, setGitlabMrPickerOpen] = React.useState(false);
const [giteaIssuePickerOpen, setGiteaIssuePickerOpen] = React.useState(false);
const [giteaPrPickerOpen, setGiteaPrPickerOpen] = React.useState(false);
const [linkedIssue, setLinkedIssue] = React.useState<{
number: number;
title: string;
@@ -686,7 +690,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
instructionsText: string;
contextText: string;
author?: { login: string; avatarUrl?: string };
provider?: 'github' | 'gitlab';
provider?: 'github' | 'gitlab' | 'gitea';
} | null>(null);
// Message queue
@@ -954,6 +958,8 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
const openIssuePicker = React.useCallback(() => {
if (gitProvider === 'gitlab') {
setGitlabIssuePickerOpen(true);
} else if (gitProvider === 'gitea') {
setGiteaIssuePickerOpen(true);
} else {
setIssuePickerOpen(true);
}
@@ -962,6 +968,8 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
const openPrPicker = React.useCallback(() => {
if (gitProvider === 'gitlab') {
setGitlabMrPickerOpen(true);
} else if (gitProvider === 'gitea') {
setGiteaPrPickerOpen(true);
} else {
setPrPickerOpen(true);
}
@@ -2914,6 +2922,23 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
setLinkedIssue(null);
}}
/>
<GiteaIssuePickerDialog
open={giteaIssuePickerOpen}
onOpenChange={setGiteaIssuePickerOpen}
mode="select"
onSelect={(issue) => {
setLinkedIssue(issue);
setLinkedPr(null);
}}
/>
<GiteaPrPickerDialog
open={giteaPrPickerOpen}
onOpenChange={setGiteaPrPickerOpen}
onSelect={(pr) => {
setLinkedPr({ ...pr, provider: 'gitea' as const });
setLinkedIssue(null);
}}
/>
<ReviewFlowDialog
open={reviewDialogOpen}
onOpenChange={setReviewDialogOpen}
@@ -2980,8 +3005,8 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
requestAnimationFrame(openIssuePicker);
}}
>
<Icon name={gitProvider === 'gitlab' ? 'gitlab' : 'github'} className="h-[18px] w-[18px] flex-shrink-0 text-muted-foreground" />
{gitProvider === 'gitlab' ? t('chat.chatInput.actions.linkGitlabIssue') : t('chat.chatInput.actions.linkGithubIssue')}
<Icon name={gitProvider === 'gitlab' ? 'gitlab' : gitProvider === 'gitea' ? 'git-branch' : 'github'} className="h-[18px] w-[18px] flex-shrink-0 text-muted-foreground" />
{gitProvider === 'gitlab' ? t('chat.chatInput.actions.linkGitlabIssue') : gitProvider === 'gitea' ? t('chat.chatInput.actions.linkGiteaIssue') : t('chat.chatInput.actions.linkGithubIssue')}
</button>
<button
type="button"
@@ -2993,7 +3018,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
}}
>
<Icon name={gitProvider === 'gitlab' ? 'gitlab' : 'git-pull-request'} className="h-[18px] w-[18px] flex-shrink-0 text-muted-foreground" />
{gitProvider === 'gitlab' ? t('chat.chatInput.actions.linkGitlabMr') : t('chat.chatInput.actions.linkGithubPr')}
{gitProvider === 'gitlab' ? t('chat.chatInput.actions.linkGitlabMr') : gitProvider === 'gitea' ? t('chat.chatInput.actions.linkGiteaPr') : t('chat.chatInput.actions.linkGithubPr')}
</button>
</div>
</MobileOverlayPanel>