diff --git a/app/components/@settings/tabs/connections/GithubConnection.tsx b/app/components/@settings/tabs/connections/GithubConnection.tsx index c6142834..e2d8924f 100644 --- a/app/components/@settings/tabs/connections/GithubConnection.tsx +++ b/app/components/@settings/tabs/connections/GithubConnection.tsx @@ -73,22 +73,13 @@ export function GithubConnection() { }); const [isLoading, setIsLoading] = useState(true); const [isConnecting, setIsConnecting] = useState(false); - const [expandedSections, setExpandedSections] = useState({ - organizations: false, - languages: false, - recentActivity: false, - repositories: false, - }); - - const toggleSection = (section: keyof typeof expandedSections) => { - setExpandedSections((prev) => ({ - ...prev, - [section]: !prev[section], - })); - }; + const [isFetchingStats, setIsFetchingStats] = useState(false); + const [isStatsExpanded, setIsStatsExpanded] = useState(false); const fetchGitHubStats = async (token: string) => { try { + setIsFetchingStats(true); + const reposResponse = await fetch( 'https://api.github.com/user/repos?sort=updated&per_page=10&affiliation=owner,organization_member,collaborator', { @@ -165,6 +156,7 @@ export function GithubConnection() { logStore.logError('Failed to fetch GitHub stats', { error }); toast.error('Failed to fetch GitHub statistics'); } finally { + setIsFetchingStats(false); } }; @@ -188,7 +180,7 @@ export function GithubConnection() { setIsLoading(false); }, []); - if (isLoading) { + if (isLoading || isConnecting || isFetchingStats) { return ; } @@ -350,7 +342,7 @@ export function GithubConnection() { 'hover:bg-red-600', )} > -
+
Disconnect )} @@ -365,161 +357,144 @@ export function GithubConnection() { {connection.user && connection.stats && (
-
- {connection.user.login} -
-

- {connection.user.name || connection.user.login} -

- {connection.user.bio && ( -

{connection.user.bio}

- )} -
- -
- {connection.user.followers} followers - - -
- {connection.user.public_repos} public repos - - -
- {connection.stats.totalStars} stars - - -
- {connection.stats.totalForks} forks - -
-
-
- - {/* Organizations Section */} - {connection.stats.organizations.length > 0 && ( -
- - {expandedSections.organizations && ( -
- {connection.stats.organizations.map((org) => ( - - {org.login} - {org.login} - - ))} + - {expandedSections.languages && ( -
- {Object.entries(connection.stats.languages) - .sort(([, a], [, b]) => b - a) - .slice(0, 5) - .map(([language]) => ( - - {language} - - ))} +
+ +
+ {connection.user.followers} followers + + +
+ {connection.user.public_repos} public repos + + +
+ {connection.stats.totalStars} stars + + +
+ {connection.stats.totalForks} forks + +
- )} -
+
+ - {/* Recent Activity Section */} -
- - {expandedSections.recentActivity && ( -
- {connection.stats.recentActivity.map((event) => ( -
-
-
- {event.type.replace('Event', '')} - on + {isStatsExpanded && ( +
+ {connection.stats.organizations.length > 0 && ( +
+

Organizations

+
+ {connection.stats.organizations.map((org) => ( - {event.repo.name} + {org.login} + {org.login} -
-
- {new Date(event.created_at).toLocaleDateString()} at{' '} - {new Date(event.created_at).toLocaleTimeString()} -
+ ))}
- ))} -
- )} -
+
+ )} - {/* Repositories Section */} -
- - {expandedSections.repositories && ( + {/* Languages Section */} +
+

Top Languages

+
+ {Object.entries(connection.stats.languages) + .sort(([, a], [, b]) => b - a) + .slice(0, 5) + .map(([language]) => ( + + {language} + + ))} +
+
+ + {/* Recent Activity Section */} +
+

Recent Activity

+
+ {connection.stats.recentActivity.map((event) => ( +
+
+
+ {event.type.replace('Event', '')} + on + + {event.repo.name} + +
+
+ {new Date(event.created_at).toLocaleDateString()} at{' '} + {new Date(event.created_at).toLocaleTimeString()} +
+
+ ))} +
+
+ + {/* Additional Stats */} +
+
+
Member Since
+
+ {new Date(connection.user.created_at).toLocaleDateString()} +
+
+
+
Public Gists
+
+ {connection.stats.totalGists} +
+
+
+
Organizations
+
+ {connection.stats.organizations.length} +
+
+
+
Languages
+
+ {Object.keys(connection.stats.languages).length} +
+
+
+ + {/* Repositories Section */} +

Recent Repositories

{connection.stats.repos.map((repo) => ( ))}
- )} -
+
+ )}
)}
diff --git a/app/components/@settings/tabs/connections/NetlifyConnection.tsx b/app/components/@settings/tabs/connections/NetlifyConnection.tsx index 490cb2d1..5881b761 100644 --- a/app/components/@settings/tabs/connections/NetlifyConnection.tsx +++ b/app/components/@settings/tabs/connections/NetlifyConnection.tsx @@ -4,8 +4,14 @@ import { toast } from 'react-toastify'; import { useStore } from '@nanostores/react'; import { logStore } from '~/lib/stores/logs'; import { classNames } from '~/utils/classNames'; -import { netlifyConnection, isConnecting, isFetchingStats, updateNetlifyConnection } from '~/lib/stores/netlify'; -import type { NetlifyUser, NetlifySite } from '~/types/netlify'; +import { + netlifyConnection, + isConnecting, + isFetchingStats, + updateNetlifyConnection, + fetchNetlifyStats, +} from '~/lib/stores/netlify'; +import type { NetlifyUser } from '~/types/netlify'; export function NetlifyConnection() { const connection = useStore(netlifyConnection); @@ -22,40 +28,6 @@ export function NetlifyConnection() { fetchSites(); }, [connection.user, connection.token]); - const fetchNetlifyStats = async (token: string) => { - try { - isFetchingStats.set(true); - - const sitesResponse = await fetch('https://api.netlify.com/api/v1/sites', { - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - }); - - if (!sitesResponse.ok) { - throw new Error(`Failed to fetch sites: ${sitesResponse.status}`); - } - - const sites = (await sitesResponse.json()) as NetlifySite[]; - - const currentState = netlifyConnection.get(); - updateNetlifyConnection({ - ...currentState, - stats: { - sites, - totalSites: sites.length, - }, - }); - } catch (error) { - console.error('Netlify API Error:', error); - logStore.logError('Failed to fetch Netlify stats', { error }); - toast.error('Failed to fetch Netlify statistics'); - } finally { - isFetchingStats.set(false); - } - }; - const handleConnect = async (event: React.FormEvent) => { event.preventDefault(); isConnecting.set(true); diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index ff1f4184..08d34afb 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -45,6 +45,7 @@ interface BaseChatProps { showChat?: boolean; chatStarted?: boolean; isStreaming?: boolean; + onStreamingChange?: (streaming: boolean) => void; messages?: Message[]; description?: string; enhancingPrompt?: boolean; @@ -79,6 +80,7 @@ export const BaseChat = React.forwardRef( showChat = true, chatStarted = false, isStreaming = false, + onStreamingChange, model, setModel, provider, @@ -126,6 +128,10 @@ export const BaseChat = React.forwardRef( console.log(transcript); }, [transcript]); + useEffect(() => { + onStreamingChange?.(isStreaming); + }, [isStreaming, onStreamingChange]); + useEffect(() => { if (typeof window !== 'undefined' && ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; @@ -337,7 +343,7 @@ export const BaseChat = React.forwardRef( }}
{ + streamingState.set(streaming); + }} enhancingPrompt={enhancingPrompt} promptEnhanced={promptEnhanced} sendMessage={sendMessage} diff --git a/app/components/chat/ChatAlert.tsx b/app/components/chat/ChatAlert.tsx index 674bbc80..5aeb08c7 100644 --- a/app/components/chat/ChatAlert.tsx +++ b/app/components/chat/ChatAlert.tsx @@ -24,7 +24,7 @@ export default function ChatAlert({ alert, clearAlert, postMessage }: Props) { animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.3 }} - className={`rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-background-depth-2 p-4`} + className={`rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-background-depth-2 p-4 mb-2`} >
{/* Icon */} diff --git a/app/components/chat/NetlifyDeploymentLink.client.tsx b/app/components/chat/NetlifyDeploymentLink.client.tsx new file mode 100644 index 00000000..da8e0b41 --- /dev/null +++ b/app/components/chat/NetlifyDeploymentLink.client.tsx @@ -0,0 +1,51 @@ +import { useStore } from '@nanostores/react'; +import { netlifyConnection, fetchNetlifyStats } from '~/lib/stores/netlify'; +import { chatId } from '~/lib/persistence/useChatHistory'; +import * as Tooltip from '@radix-ui/react-tooltip'; +import { useEffect } from 'react'; + +export function NetlifyDeploymentLink() { + const connection = useStore(netlifyConnection); + const currentChatId = useStore(chatId); + + useEffect(() => { + if (connection.token && currentChatId) { + fetchNetlifyStats(connection.token); + } + }, [connection.token, currentChatId]); + + const deployedSite = connection.stats?.sites?.find((site) => site.name.includes(`bolt-diy-${currentChatId}`)); + + if (!deployedSite) { + return null; + } + + return ( + + + + { + e.stopPropagation(); // Add this to prevent click from bubbling up + }} + > +
+ + + + + {deployedSite.url} + + + + + + ); +} diff --git a/app/components/chat/ProgressCompilation.tsx b/app/components/chat/ProgressCompilation.tsx index 270fac03..68ae3388 100644 --- a/app/components/chat/ProgressCompilation.tsx +++ b/app/components/chat/ProgressCompilation.tsx @@ -42,7 +42,6 @@ export default function ProgressCompilation({ data }: { data?: ProgressAnnotatio 'shadow-lg rounded-lg relative w-full max-w-chat mx-auto z-prompt', 'p-1', )} - style={{ transform: 'translateY(1rem)' }} >
(null); + const isStreaming = useStore(streamingState); useEffect(() => { function handleClickOutside(event: MouseEvent) { @@ -41,7 +44,7 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) { const handleDeploy = async () => { if (!connection.user || !connection.token) { - toast.error('Please connect to Netlify first'); + toast.error('Please connect to Netlify first in the settings tab!'); return; } @@ -206,7 +209,7 @@ export function HeaderActionButtons({}: HeaderActionButtonsProps) {