Files
bolt-diy/app/components/chat/GitCloneButton.tsx
Stijnus 3ea96506ea feat: gitLab Integration Implementation / github refactor / overal improvements (#1963)
* Add GitLab integration components

Introduced PushToGitLabDialog and GitlabConnection components to handle GitLab project connections and push functionality. Includes user authentication, project handling, and UI for seamless integration with GitLab.

* Add components for GitLab connection and push dialog

Introduce `GitlabConnection` and `PushToGitLabDialog` components to handle GitLab integration. These components allow users to connect their GitLab account, manage recent projects, and push code to a GitLab repository with detailed configurations and feedback.

* Fix GitLab personal access tokens link to use correct URL

* Update GitHub push call to use new pushToRepository method

* Enhance GitLab integration with performance improvements

- Add comprehensive caching system for repositories and user data
- Implement pagination and search/filter functionality with debouncing
- Add skeleton loaders and improved loading states
- Implement retry logic for API calls with exponential backoff
- Add background refresh capabilities
- Improve error handling and user feedback
- Optimize API calls to reduce loading times

* feat: implement GitLab integration with connection management and repository handling

- Add GitLab connection UI components
- Implement GitLab API service for repository operations
- Add GitLab connection store for state management
- Update existing connection components (Vercel, Netlify)
- Add repository listing and statistics display
- Refactor GitLab components into organized folder structure

* fix: resolve GitLab deployment issues and improve user experience

- Fix DialogTitle accessibility warnings for screen readers
- Remove CORS-problematic attributes from avatar images to prevent loading errors
- Enhance GitLab API error handling with detailed error messages
- Fix project creation settings to prevent initial commit conflicts
- Add automatic GitLab connection state initialization on app startup
- Improve deployment dialog UI with better error handling and user feedback
- Add GitLab deployment source type to action runner system
- Clean up deprecated push dialog files and consolidate deployment components

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: implement GitHub clone repository dialog functionality

This commit fixes the missing GitHub repository selection dialog in the "Clone a repo" feature
by implementing the same elegant interface pattern used by GitLab.

Key Changes:
- Added onCloneRepository prop support to GitHubConnection component
- Updated RepositoryCard to generate proper GitHub clone URLs (https://github.com/{full_name}.git)
- Implemented full GitHub repository selection dialog in GitCloneButton.tsx
- Added proper dialog close handling after successful clone operations
- Maintained existing GitHub connection settings page functionality

Technical Details:
- Follows same component patterns as GitLab implementation
- Uses proper TypeScript interfaces for clone URL handling
- Includes professional dialog styling with loading states
- Supports repository search, pagination, and authentication flow

The GitHub clone experience now matches GitLab's functionality, providing users with
a unified and intuitive repository selection interface across both providers.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Clean up unused connection components

- Remove ConnectionForm.tsx (unused GitHub form component)
- Remove CreateBranchDialog.tsx (unused branch creation dialog)
- Remove RepositoryDialogContext.tsx (unused context provider)
- Remove empty components/ directory

These files were not referenced anywhere in the codebase and were leftover from development.

* Remove environment variables info section from ConnectionsTab

- Remove collapsible environment variables section
- Clean up unused state and imports
- Simplify the connections tab UI

* Reorganize connections folder structure

- Create netlify/ folder and move NetlifyConnection.tsx
- Create vercel/ folder and move VercelConnection.tsx
- Add index.ts files for both netlify and vercel folders
- Update imports in ConnectionsTab.tsx to use new folder structure
- All connection components now follow consistent folder organization

---------

Co-authored-by: Hayat Bourgi <hayat.bourgi@montyholding.com>
Co-authored-by: Hayat55 <53140162+Hayat55@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-09-05 14:01:33 +02:00

329 lines
14 KiB
TypeScript

import ignore from 'ignore';
import { useGit } from '~/lib/hooks/useGit';
import type { Message } from 'ai';
import { detectProjectCommands, createCommandsMessage, escapeBoltTags } from '~/utils/projectCommands';
import { generateId } from '~/utils/fileUtils';
import { useState } from 'react';
import { toast } from 'react-toastify';
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
// import { RepositorySelectionDialog } from '~/components/@settings/tabs/connections/components/RepositorySelectionDialog';
import { classNames } from '~/utils/classNames';
import { Button } from '~/components/ui/Button';
import type { IChatMetadata } from '~/lib/persistence/db';
import { X, Github, GitBranch } from 'lucide-react';
// Import GitLab and GitHub connections for unified repository access
import GitLabConnection from '~/components/@settings/tabs/connections/gitlab/GitLabConnection';
import GitHubConnection from '~/components/@settings/tabs/connections/github/GitHubConnection';
const IGNORE_PATTERNS = [
'node_modules/**',
'.git/**',
'.github/**',
'.vscode/**',
'dist/**',
'build/**',
'.next/**',
'coverage/**',
'.cache/**',
'.idea/**',
'**/*.log',
'**/.DS_Store',
'**/npm-debug.log*',
'**/yarn-debug.log*',
'**/yarn-error.log*',
// Include this so npm install runs much faster '**/*lock.json',
'**/*lock.yaml',
];
const ig = ignore().add(IGNORE_PATTERNS);
const MAX_FILE_SIZE = 100 * 1024; // 100KB limit per file
const MAX_TOTAL_SIZE = 500 * 1024; // 500KB total limit
interface GitCloneButtonProps {
className?: string;
importChat?: (description: string, messages: Message[], metadata?: IChatMetadata) => Promise<void>;
}
export default function GitCloneButton({ importChat, className }: GitCloneButtonProps) {
const { ready, gitClone } = useGit();
const [loading, setLoading] = useState(false);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [selectedProvider, setSelectedProvider] = useState<'github' | 'gitlab' | null>(null);
const handleClone = async (repoUrl: string) => {
if (!ready) {
return;
}
setLoading(true);
setIsDialogOpen(false);
setSelectedProvider(null);
try {
const { workdir, data } = await gitClone(repoUrl);
if (importChat) {
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
const textDecoder = new TextDecoder('utf-8');
let totalSize = 0;
const skippedFiles: string[] = [];
const fileContents = [];
for (const filePath of filePaths) {
const { data: content, encoding } = data[filePath];
// Skip binary files
if (
content instanceof Uint8Array &&
!filePath.match(/\.(txt|md|astro|mjs|js|jsx|ts|tsx|json|html|css|scss|less|yml|yaml|xml|svg|vue|svelte)$/i)
) {
skippedFiles.push(filePath);
continue;
}
try {
const textContent =
encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '';
if (!textContent) {
continue;
}
// Check file size
const fileSize = new TextEncoder().encode(textContent).length;
if (fileSize > MAX_FILE_SIZE) {
skippedFiles.push(`${filePath} (too large: ${Math.round(fileSize / 1024)}KB)`);
continue;
}
// Check total size
if (totalSize + fileSize > MAX_TOTAL_SIZE) {
skippedFiles.push(`${filePath} (would exceed total size limit)`);
continue;
}
totalSize += fileSize;
fileContents.push({
path: filePath,
content: textContent,
});
} catch (e: any) {
skippedFiles.push(`${filePath} (error: ${e.message})`);
}
}
const commands = await detectProjectCommands(fileContents);
const commandsMessage = createCommandsMessage(commands);
const filesMessage: Message = {
role: 'assistant',
content: `Cloning the repo ${repoUrl} into ${workdir}
${
skippedFiles.length > 0
? `\nSkipped files (${skippedFiles.length}):
${skippedFiles.map((f) => `- ${f}`).join('\n')}`
: ''
}
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">
${fileContents
.map(
(file) =>
`<boltAction type="file" filePath="${file.path}">
${escapeBoltTags(file.content)}
</boltAction>`,
)
.join('\n')}
</boltArtifact>`,
id: generateId(),
createdAt: new Date(),
};
const messages = [filesMessage];
if (commandsMessage) {
messages.push(commandsMessage);
}
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
}
} catch (error) {
console.error('Error during import:', error);
toast.error('Failed to import repository');
} finally {
setLoading(false);
}
};
return (
<>
<Button
onClick={() => {
setSelectedProvider(null);
setIsDialogOpen(true);
}}
title="Clone a repo"
variant="default"
size="lg"
className={classNames(
'gap-2 bg-bolt-elements-background-depth-1',
'text-bolt-elements-textPrimary',
'hover:bg-bolt-elements-background-depth-2',
'border border-bolt-elements-borderColor',
'h-10 px-4 py-2 min-w-[120px] justify-center',
'transition-all duration-200 ease-in-out',
className,
)}
disabled={!ready || loading}
>
Clone a repo
<div className="flex items-center gap-1 ml-2">
<Github className="w-4 h-4" />
<GitBranch className="w-4 h-4" />
</div>
</Button>
{/* Provider Selection Dialog */}
{isDialogOpen && !selectedProvider && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor max-w-md w-full">
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
Choose Repository Provider
</h3>
<button
onClick={() => setIsDialogOpen(false)}
className="p-2 rounded-lg bg-transparent hover:bg-bolt-elements-background-depth-1 dark:hover:bg-bolt-elements-background-depth-1 text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary dark:hover:text-bolt-elements-textPrimary transition-all duration-200 hover:scale-105 active:scale-95"
>
<X className="w-5 h-5 transition-transform duration-200 hover:rotate-90" />
</button>
</div>
<div className="space-y-3">
<button
onClick={() => setSelectedProvider('github')}
className="w-full p-4 rounded-lg bg-bolt-elements-background-depth-1 dark:bg-bolt-elements-background-depth-1 hover:bg-bolt-elements-background-depth-2 dark:hover:bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor hover:border-bolt-elements-borderColorActive dark:hover:border-bolt-elements-borderColorActive transition-all duration-200 text-left group"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-blue-500/10 dark:bg-blue-500/20 flex items-center justify-center group-hover:bg-blue-500/20 dark:group-hover:bg-blue-500/30 transition-colors">
<Github className="w-6 h-6 text-blue-600 dark:text-blue-400" />
</div>
<div>
<div className="font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
GitHub
</div>
<div className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
Clone from GitHub repositories
</div>
</div>
</div>
</button>
<button
onClick={() => setSelectedProvider('gitlab')}
className="w-full p-4 rounded-lg bg-bolt-elements-background-depth-1 dark:bg-bolt-elements-background-depth-1 hover:bg-bolt-elements-background-depth-2 dark:hover:bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor hover:border-bolt-elements-borderColorActive dark:hover:border-bolt-elements-borderColorActive transition-all duration-200 text-left group"
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-orange-500/10 dark:bg-orange-500/20 flex items-center justify-center group-hover:bg-orange-500/20 dark:group-hover:bg-orange-500/30 transition-colors">
<GitBranch className="w-6 h-6 text-orange-600 dark:text-orange-400" />
</div>
<div>
<div className="font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
GitLab
</div>
<div className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
Clone from GitLab repositories
</div>
</div>
</div>
</button>
</div>
</div>
</div>
</div>
)}
{/* GitHub Repository Selection */}
{isDialogOpen && selectedProvider === 'github' && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor w-full max-w-4xl max-h-[90vh] overflow-hidden">
<div className="p-6 border-b border-bolt-elements-borderColor dark:border-bolt-elements-borderColor flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-blue-500/10 dark:bg-blue-500/20 flex items-center justify-center">
<Github className="w-6 h-6 text-blue-600 dark:text-blue-400" />
</div>
<div>
<h3 className="text-lg font-semibold text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
Import GitHub Repository
</h3>
<p className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
Clone a repository from GitHub to your workspace
</p>
</div>
</div>
<button
onClick={() => {
setIsDialogOpen(false);
setSelectedProvider(null);
}}
className="p-2 rounded-lg bg-transparent hover:bg-bolt-elements-background-depth-1 dark:hover:bg-bolt-elements-background-depth-1 text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary dark:hover:text-bolt-elements-textPrimary transition-all duration-200 hover:scale-105 active:scale-95"
>
<X className="w-5 h-5 transition-transform duration-200 hover:rotate-90" />
</button>
</div>
<div className="p-6 max-h-[calc(90vh-140px)] overflow-y-auto">
<GitHubConnection onCloneRepository={handleClone} />
</div>
</div>
</div>
)}
{/* GitLab Repository Selection */}
{isDialogOpen && selectedProvider === 'gitlab' && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor w-full max-w-4xl max-h-[90vh] overflow-hidden">
<div className="p-6 border-b border-bolt-elements-borderColor dark:border-bolt-elements-borderColor flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-orange-500/10 dark:bg-orange-500/20 flex items-center justify-center">
<GitBranch className="w-6 h-6 text-orange-600 dark:text-orange-400" />
</div>
<div>
<h3 className="text-lg font-semibold text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
Import GitLab Repository
</h3>
<p className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary">
Clone a repository from GitLab to your workspace
</p>
</div>
</div>
<button
onClick={() => {
setIsDialogOpen(false);
setSelectedProvider(null);
}}
className="p-2 rounded-lg bg-transparent hover:bg-bolt-elements-background-depth-1 dark:hover:bg-bolt-elements-background-depth-1 text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary dark:hover:text-bolt-elements-textPrimary transition-all duration-200 hover:scale-105 active:scale-95"
>
<X className="w-5 h-5 transition-transform duration-200 hover:rotate-90" />
</button>
</div>
<div className="p-6 max-h-[calc(90vh-140px)] overflow-y-auto">
<GitLabConnection onCloneRepository={handleClone} />
</div>
</div>
</div>
)}
{loading && <LoadingOverlay message="Please wait while we clone the repository..." />}
</>
);
}