Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: merge my own set #3

Merged
merged 19 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/commit.json

This file was deleted.

2 changes: 1 addition & 1 deletion app/components/chat/GitCloneButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import ignore from 'ignore';
import { useGit } from '~/lib/hooks/useGit';
import type { Message } from 'ai';
import { detectProjectCommands, createCommandsMessage } from '~/utils/projectCommands';
import { generateId } from '~/utils/fileUtils';
import { useGit } from '~/lib/git';

const IGNORE_PATTERNS = [
'node_modules/**',
Expand Down
2 changes: 1 addition & 1 deletion app/components/git/GitUrlImport.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { useEffect, useState } from 'react';
import { ClientOnly } from 'remix-utils/client-only';
import { BaseChat } from '~/components/chat/BaseChat';
import { Chat } from '~/components/chat/Chat.client';
import { useGit } from '~/lib/hooks/useGit';
import { useChatHistory } from '~/lib/persistence';
import { createCommandsMessage, detectProjectCommands } from '~/utils/projectCommands';
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
import { toast } from 'react-toastify';
import { useGit } from '~/lib/git';

const IGNORE_PATTERNS = [
'node_modules/**',
Expand Down
173 changes: 29 additions & 144 deletions app/components/settings/connections/ConnectionsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,151 +1,36 @@
import React, { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import Cookies from 'js-cookie';
import { logStore } from '~/lib/stores/logs';

interface GitHubUserResponse {
login: string;
id: number;
[key: string]: any; // for other properties we don't explicitly need
}
import React from 'react';
import { ProviderCard } from '~/lib/git/components/ProviderCard';
import { useGitProviders } from '~/lib/git/hooks/useGitProviders';

export default function ConnectionsTab() {
const [githubUsername, setGithubUsername] = useState(Cookies.get('githubUsername') || '');
const [githubToken, setGithubToken] = useState(Cookies.get('githubToken') || '');
const [isConnected, setIsConnected] = useState(false);
const [isVerifying, setIsVerifying] = useState(false);

useEffect(() => {
// Check if credentials exist and verify them
if (githubUsername && githubToken) {
verifyGitHubCredentials();
}
}, []);

const verifyGitHubCredentials = async () => {
setIsVerifying(true);

try {
const response = await fetch('https://api.github.com/user', {
headers: {
Authorization: `Bearer ${githubToken}`,
},
});

if (response.ok) {
const data = (await response.json()) as GitHubUserResponse;

if (data.login === githubUsername) {
setIsConnected(true);
return true;
}
}

setIsConnected(false);

return false;
} catch (error) {
console.error('Error verifying GitHub credentials:', error);
setIsConnected(false);

return false;
} finally {
setIsVerifying(false);
}
};

const handleSaveConnection = async () => {
if (!githubUsername || !githubToken) {
toast.error('Please provide both GitHub username and token');
return;
}

setIsVerifying(true);

const isValid = await verifyGitHubCredentials();

if (isValid) {
Cookies.set('githubUsername', githubUsername);
Cookies.set('githubToken', githubToken);
logStore.logSystem('GitHub connection settings updated', {
username: githubUsername,
hasToken: !!githubToken,
});
toast.success('GitHub credentials verified and saved successfully!');
Cookies.set('git:github.com', JSON.stringify({ username: githubToken, password: 'x-oauth-basic' }));
setIsConnected(true);
} else {
toast.error('Invalid GitHub credentials. Please check your username and token.');
}
};

const handleDisconnect = () => {
Cookies.remove('githubUsername');
Cookies.remove('githubToken');
Cookies.remove('git:github.com');
setGithubUsername('');
setGithubToken('');
setIsConnected(false);
logStore.logSystem('GitHub connection removed');
toast.success('GitHub connection removed successfully!');
};
const {
providers,
credentials,
expandedProviders,
handleSaveConnection,
handleDisconnect,
updateProviderCredentials,
toggleProvider,
} = useGitProviders();

return (
<div className="p-4 mb-4 border border-bolt-elements-borderColor rounded-lg bg-bolt-elements-background-depth-3">
<h3 className="text-lg font-medium text-bolt-elements-textPrimary mb-4">GitHub Connection</h3>
<div className="flex mb-4">
<div className="flex-1 mr-2">
<label className="block text-sm text-bolt-elements-textSecondary mb-1">GitHub Username:</label>
<input
type="text"
value={githubUsername}
onChange={(e) => setGithubUsername(e.target.value)}
disabled={isVerifying}
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50"
/>
</div>
<div className="flex-1">
<label className="block text-sm text-bolt-elements-textSecondary mb-1">Personal Access Token:</label>
<input
type="password"
value={githubToken}
onChange={(e) => setGithubToken(e.target.value)}
disabled={isVerifying}
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor disabled:opacity-50"
/>
</div>
</div>
<div className="flex mb-4 items-center">
{!isConnected ? (
<button
onClick={handleSaveConnection}
disabled={isVerifying || !githubUsername || !githubToken}
className="bg-bolt-elements-button-primary-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-button-primary-text disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
>
{isVerifying ? (
<>
<div className="i-ph:spinner animate-spin mr-2" />
Verifying...
</>
) : (
'Connect'
)}
</button>
) : (
<button
onClick={handleDisconnect}
className="bg-bolt-elements-button-danger-background rounded-lg px-4 py-2 mr-2 transition-colors duration-200 hover:bg-bolt-elements-button-danger-backgroundHover text-bolt-elements-button-danger-text"
>
Disconnect
</button>
)}
{isConnected && (
<span className="text-sm text-green-600 flex items-center">
<div className="i-ph:check-circle mr-1" />
Connected to GitHub
</span>
)}
</div>
<div className="space-y-4">
<div className="i-ph:github-logo-duotone hidden" />
// Preloading icons otherwise they will not be displayed. Need fixing.
<div className="i-ph:gitlab-logo-duotone hidden" />
// Preloading icons otherwise they will not be displayed. Need fixing.
{Object.entries(providers).map(([key, plugin]) => (
<ProviderCard
key={key}
provider={plugin.provider}
credentials={credentials[key]}
isExpanded={expandedProviders[key]}
onToggle={() => toggleProvider(key)}
onUpdateCredentials={(updates) => updateProviderCredentials(key, updates)}
onSave={() => handleSaveConnection(key)}
onDisconnect={() => handleDisconnect(key)}
/>
))}
</div>
);
}
21 changes: 9 additions & 12 deletions app/components/settings/providers/ProvidersTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export default function ProvidersTab() {
newFilteredProviders.sort((a, b) => a.name.localeCompare(b.name));

// Split providers into regular and URL-configurable
const regular = newFilteredProviders.filter(p => !URL_CONFIGURABLE_PROVIDERS.includes(p.name));
const urlConfigurable = newFilteredProviders.filter(p => URL_CONFIGURABLE_PROVIDERS.includes(p.name));
const regular = newFilteredProviders.filter((p) => !URL_CONFIGURABLE_PROVIDERS.includes(p.name));
const urlConfigurable = newFilteredProviders.filter((p) => URL_CONFIGURABLE_PROVIDERS.includes(p.name));

setFilteredProviders([...regular, ...urlConfigurable]);
}, [providers, searchTerm, isLocalModel]);
Expand Down Expand Up @@ -112,8 +112,8 @@ export default function ProvidersTab() {
);
};

const regularProviders = filteredProviders.filter(p => !URL_CONFIGURABLE_PROVIDERS.includes(p.name));
const urlConfigurableProviders = filteredProviders.filter(p => URL_CONFIGURABLE_PROVIDERS.includes(p.name));
const regularProviders = filteredProviders.filter((p) => !URL_CONFIGURABLE_PROVIDERS.includes(p.name));
const urlConfigurableProviders = filteredProviders.filter((p) => URL_CONFIGURABLE_PROVIDERS.includes(p.name));

return (
<div className="p-4">
Expand All @@ -128,22 +128,19 @@ export default function ProvidersTab() {
</div>

{/* Regular Providers Grid */}
<div className="grid grid-cols-2 gap-4 mb-8">
{regularProviders.map(renderProviderCard)}
</div>
<div className="grid grid-cols-2 gap-4 mb-8">{regularProviders.map(renderProviderCard)}</div>

{/* URL Configurable Providers Section */}
{urlConfigurableProviders.length > 0 && (
<div className="mt-8">
<h3 className="text-lg font-semibold mb-2 text-bolt-elements-textPrimary">Experimental Providers</h3>
<p className="text-sm text-bolt-elements-textSecondary mb-4">
These providers are experimental and allow you to run AI models locally or connect to your own infrastructure. They require additional setup but offer more flexibility.
These providers are experimental and allow you to run AI models locally or connect to your own
infrastructure. They require additional setup but offer more flexibility.
</p>
<div className="space-y-4">
{urlConfigurableProviders.map(renderProviderCard)}
</div>
<div className="space-y-4">{urlConfigurableProviders.map(renderProviderCard)}</div>
</div>
)}
</div>
);
}
}
83 changes: 47 additions & 36 deletions app/components/workbench/Workbench.client.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useStore } from '@nanostores/react';
import { motion, type HTMLMotionProps, type Variants } from 'framer-motion';
import { computed } from 'nanostores';
import { memo, useCallback, useEffect, useState } from 'react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
import {
type OnChangeCallback as OnEditorChange,
Expand All @@ -17,7 +17,7 @@ import { renderLogger } from '~/utils/logger';
import { EditorPanel } from './EditorPanel';
import { Preview } from './Preview';
import useViewport from '~/lib/hooks';
import Cookies from 'js-cookie';
import { getGitCredentials, createGitPushHandler, gitProviders } from '~/lib/git';

interface WorkspaceProps {
chatStarted?: boolean;
Expand Down Expand Up @@ -58,6 +58,10 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
renderLogger.trace('Workbench');

const [isSyncing, setIsSyncing] = useState(false);
const [hasCredentials, setHasCredentials] = useState<{ github: boolean; gitlab: boolean }>({
github: false,
gitlab: false,
});

const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
const showWorkbench = useStore(workbenchStore.showWorkbench);
Expand All @@ -83,6 +87,17 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
workbenchStore.setDocuments(files);
}, [files]);

useEffect(() => {
const initCredentials = async () => {
const credentials = await getGitCredentials();
setHasCredentials({
github: credentials.github || false,
gitlab: credentials.gitlab || false,
});
};
initCredentials();
}, []);

const onEditorChange = useCallback<OnEditorChange>((update) => {
workbenchStore.setCurrentDocumentContent(update.content);
}, []);
Expand Down Expand Up @@ -120,6 +135,26 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
}
}, []);

const cleanPath = (path: string): string => {
return path.replace('/home/project/', '');
};

const getWorkbenchFiles = useCallback(() => {
const docs = workbenchStore.files.get();
return Object.entries(docs).reduce(
(acc, [path, doc]) => {
if (doc && 'content' in doc) {
acc[cleanPath(path)] = (doc as { content: string }).content;
}

return acc;
},
{} as Record<string, string>,
);
}, []);

const pushFunctions = useMemo(() => createGitPushHandler(getWorkbenchFiles), [getWorkbenchFiles]);

return (
chatStarted && (
<motion.div
Expand Down Expand Up @@ -168,40 +203,15 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
<div className="i-ph:terminal" />
Toggle Terminal
</PanelHeaderButton>
<PanelHeaderButton
className="mr-1 text-sm"
onClick={() => {
const repoName = prompt(
'Please enter a name for your new GitHub repository:',
'bolt-generated-project',
);

if (!repoName) {
alert('Repository name is required. Push to GitHub cancelled.');
return;
}

const githubUsername = Cookies.get('githubUsername');
const githubToken = Cookies.get('githubToken');

if (!githubUsername || !githubToken) {
const usernameInput = prompt('Please enter your GitHub username:');
const tokenInput = prompt('Please enter your GitHub personal access token:');

if (!usernameInput || !tokenInput) {
alert('GitHub username and token are required. Push to GitHub cancelled.');
return;
}

workbenchStore.pushToGitHub(repoName, usernameInput, tokenInput);
} else {
workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
}
}}
>
<div className="i-ph:github-logo" />
Push to GitHub
</PanelHeaderButton>
{Object.entries(gitProviders).map(
([name, plugin]) =>
hasCredentials[name as keyof typeof hasCredentials] && (
<PanelHeaderButton key={name} className="mr-1 text-sm" onClick={pushFunctions[name]}>
<div className={plugin.provider.icon} />
Push to {plugin.provider.title}
</PanelHeaderButton>
),
)}
</div>
)}
<IconButton
Expand Down Expand Up @@ -245,6 +255,7 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
)
);
});

interface ViewProps extends HTMLMotionProps<'div'> {
children: JSX.Element;
}
Expand Down
Loading
Loading