Skip to content

Commit 6d4196a

Browse files
authored
fix: improve push to github option (#1111)
* feat: better push to githubbutton * added url update on push to github
1 parent df766c9 commit 6d4196a

File tree

7 files changed

+95
-19
lines changed

7 files changed

+95
-19
lines changed

app/components/chat/GitCloneButton.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { generateId } from '~/utils/fileUtils';
66
import { useState } from 'react';
77
import { toast } from 'react-toastify';
88
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
9+
import type { IChatMetadata } from '~/lib/persistence';
910

1011
const IGNORE_PATTERNS = [
1112
'node_modules/**',
@@ -35,7 +36,7 @@ const ig = ignore().add(IGNORE_PATTERNS);
3536

3637
interface GitCloneButtonProps {
3738
className?: string;
38-
importChat?: (description: string, messages: Message[]) => Promise<void>;
39+
importChat?: (description: string, messages: Message[], metadata?: IChatMetadata) => Promise<void>;
3940
}
4041

4142
export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
@@ -98,7 +99,7 @@ ${file.content}
9899
messages.push(commandsMessage);
99100
}
100101

101-
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
102+
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages, { gitUrl: repoUrl });
102103
}
103104
} catch (error) {
104105
console.error('Error during import:', error);

app/components/git/GitUrlImport.client.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ ${file.content}
9494
messages.push(commandsMessage);
9595
}
9696

97-
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
97+
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages, { gitUrl: repoUrl });
9898
}
9999
} catch (error) {
100100
console.error('Error during import:', error);

app/components/workbench/Workbench.client.tsx

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { EditorPanel } from './EditorPanel';
1818
import { Preview } from './Preview';
1919
import useViewport from '~/lib/hooks';
2020
import Cookies from 'js-cookie';
21+
import { chatMetadata, useChatHistory } from '~/lib/persistence';
2122

2223
interface WorkspaceProps {
2324
chatStarted?: boolean;
@@ -66,6 +67,8 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
6667
const unsavedFiles = useStore(workbenchStore.unsavedFiles);
6768
const files = useStore(workbenchStore.files);
6869
const selectedView = useStore(workbenchStore.currentView);
70+
const metadata = useStore(chatMetadata);
71+
const { updateChatMestaData } = useChatHistory();
6972

7073
const isSmallViewport = useViewport(1024);
7174

@@ -171,18 +174,28 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
171174
<PanelHeaderButton
172175
className="mr-1 text-sm"
173176
onClick={() => {
174-
const repoName = prompt(
175-
'Please enter a name for your new GitHub repository:',
176-
'bolt-generated-project',
177-
);
177+
let repoName = metadata?.gitUrl?.split('/').slice(-1)[0]?.replace('.git', '') || null;
178+
let repoConfirmed: boolean = true;
179+
180+
if (repoName) {
181+
repoConfirmed = confirm(`Do you want to push to the repository ${repoName}?`);
182+
}
183+
184+
if (!repoName || !repoConfirmed) {
185+
repoName = prompt(
186+
'Please enter a name for your new GitHub repository:',
187+
'bolt-generated-project',
188+
);
189+
} else {
190+
}
178191

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

184-
const githubUsername = Cookies.get('githubUsername');
185-
const githubToken = Cookies.get('githubToken');
197+
let githubUsername = Cookies.get('githubUsername');
198+
let githubToken = Cookies.get('githubToken');
186199

187200
if (!githubUsername || !githubToken) {
188201
const usernameInput = prompt('Please enter your GitHub username:');
@@ -193,9 +206,26 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
193206
return;
194207
}
195208

196-
workbenchStore.pushToGitHub(repoName, usernameInput, tokenInput);
197-
} else {
198-
workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
209+
githubUsername = usernameInput;
210+
githubToken = tokenInput;
211+
212+
Cookies.set('githubUsername', usernameInput);
213+
Cookies.set('githubToken', tokenInput);
214+
Cookies.set(
215+
'git:github.com',
216+
JSON.stringify({ username: tokenInput, password: 'x-oauth-basic' }),
217+
);
218+
}
219+
220+
const commitMessage =
221+
prompt('Please enter a commit message:', 'Initial commit') || 'Initial commit';
222+
workbenchStore.pushToGitHub(repoName, commitMessage, githubUsername, githubToken);
223+
224+
if (!metadata?.gitUrl) {
225+
updateChatMestaData({
226+
...(metadata || {}),
227+
gitUrl: `https://github.com/${githubUsername}/${repoName}.git`,
228+
});
199229
}
200230
}}
201231
>

app/lib/hooks/useGit.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export function useGit() {
9292
},
9393
onAuthFailure: (url, _auth) => {
9494
toast.error(`Error Authenticating with ${url.split('/')[2]}`);
95+
throw `Error Authenticating with ${url.split('/')[2]}`;
9596
},
9697
onAuthSuccess: (url, auth) => {
9798
saveGitAuth(url, auth);
@@ -107,6 +108,8 @@ export function useGit() {
107108
return { workdir: webcontainer.workdir, data };
108109
} catch (error) {
109110
console.error('Git clone error:', error);
111+
112+
// toast.error(`Git clone error ${(error as any).message||""}`);
110113
throw error;
111114
}
112115
},

app/lib/persistence/db.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import type { Message } from 'ai';
22
import { createScopedLogger } from '~/utils/logger';
33
import type { ChatHistoryItem } from './useChatHistory';
44

5+
export interface IChatMetadata {
6+
gitUrl: string;
7+
gitBranch?: string;
8+
}
9+
510
const logger = createScopedLogger('ChatHistory');
611

712
// this is used at the top level and never rejects
@@ -53,6 +58,7 @@ export async function setMessages(
5358
urlId?: string,
5459
description?: string,
5560
timestamp?: string,
61+
metadata?: IChatMetadata,
5662
): Promise<void> {
5763
return new Promise((resolve, reject) => {
5864
const transaction = db.transaction('chats', 'readwrite');
@@ -69,6 +75,7 @@ export async function setMessages(
6975
urlId,
7076
description,
7177
timestamp: timestamp ?? new Date().toISOString(),
78+
metadata,
7279
});
7380

7481
request.onsuccess = () => resolve();
@@ -204,6 +211,7 @@ export async function createChatFromMessages(
204211
db: IDBDatabase,
205212
description: string,
206213
messages: Message[],
214+
metadata?: IChatMetadata,
207215
): Promise<string> {
208216
const newId = await getNextId(db);
209217
const newUrlId = await getUrlId(db, newId); // Get a new urlId for the duplicated chat
@@ -214,6 +222,8 @@ export async function createChatFromMessages(
214222
messages,
215223
newUrlId, // Use the new urlId
216224
description,
225+
undefined, // Use the current timestamp
226+
metadata,
217227
);
218228

219229
return newUrlId; // Return the urlId instead of id for navigation
@@ -230,5 +240,19 @@ export async function updateChatDescription(db: IDBDatabase, id: string, descrip
230240
throw new Error('Description cannot be empty');
231241
}
232242

233-
await setMessages(db, id, chat.messages, chat.urlId, description, chat.timestamp);
243+
await setMessages(db, id, chat.messages, chat.urlId, description, chat.timestamp, chat.metadata);
244+
}
245+
246+
export async function updateChatMetadata(
247+
db: IDBDatabase,
248+
id: string,
249+
metadata: IChatMetadata | undefined,
250+
): Promise<void> {
251+
const chat = await getMessages(db, id);
252+
253+
if (!chat) {
254+
throw new Error('Chat not found');
255+
}
256+
257+
await setMessages(db, id, chat.messages, chat.urlId, chat.description, chat.timestamp, metadata);
234258
}

app/lib/persistence/useChatHistory.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
setMessages,
1414
duplicateChat,
1515
createChatFromMessages,
16+
type IChatMetadata,
1617
} from './db';
1718

1819
export interface ChatHistoryItem {
@@ -21,6 +22,7 @@ export interface ChatHistoryItem {
2122
description?: string;
2223
messages: Message[];
2324
timestamp: string;
25+
metadata?: IChatMetadata;
2426
}
2527

2628
const persistenceEnabled = !import.meta.env.VITE_DISABLE_PERSISTENCE;
@@ -29,7 +31,7 @@ export const db = persistenceEnabled ? await openDatabase() : undefined;
2931

3032
export const chatId = atom<string | undefined>(undefined);
3133
export const description = atom<string | undefined>(undefined);
32-
34+
export const chatMetadata = atom<IChatMetadata | undefined>(undefined);
3335
export function useChatHistory() {
3436
const navigate = useNavigate();
3537
const { id: mixedId } = useLoaderData<{ id?: string }>();
@@ -65,6 +67,7 @@ export function useChatHistory() {
6567
setUrlId(storedMessages.urlId);
6668
description.set(storedMessages.description);
6769
chatId.set(storedMessages.id);
70+
chatMetadata.set(storedMessages.metadata);
6871
} else {
6972
navigate('/', { replace: true });
7073
}
@@ -81,6 +84,21 @@ export function useChatHistory() {
8184
return {
8285
ready: !mixedId || ready,
8386
initialMessages,
87+
updateChatMestaData: async (metadata: IChatMetadata) => {
88+
const id = chatId.get();
89+
90+
if (!db || !id) {
91+
return;
92+
}
93+
94+
try {
95+
await setMessages(db, id, initialMessages, urlId, description.get(), undefined, metadata);
96+
chatMetadata.set(metadata);
97+
} catch (error) {
98+
toast.error('Failed to update chat metadata');
99+
console.error(error);
100+
}
101+
},
84102
storeMessageHistory: async (messages: Message[]) => {
85103
if (!db || messages.length === 0) {
86104
return;
@@ -109,7 +127,7 @@ export function useChatHistory() {
109127
}
110128
}
111129

112-
await setMessages(db, chatId.get() as string, messages, urlId, description.get());
130+
await setMessages(db, chatId.get() as string, messages, urlId, description.get(), undefined, chatMetadata.get());
113131
},
114132
duplicateCurrentChat: async (listItemId: string) => {
115133
if (!db || (!mixedId && !listItemId)) {
@@ -125,13 +143,13 @@ export function useChatHistory() {
125143
console.log(error);
126144
}
127145
},
128-
importChat: async (description: string, messages: Message[]) => {
146+
importChat: async (description: string, messages: Message[], metadata?: IChatMetadata) => {
129147
if (!db) {
130148
return;
131149
}
132150

133151
try {
134-
const newId = await createChatFromMessages(db, description, messages);
152+
const newId = await createChatFromMessages(db, description, messages, metadata);
135153
window.location.href = `/chat/${newId}`;
136154
toast.success('Chat imported successfully');
137155
} catch (error) {

app/lib/stores/workbench.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ export class WorkbenchStore {
434434
return syncedFiles;
435435
}
436436

437-
async pushToGitHub(repoName: string, githubUsername?: string, ghToken?: string) {
437+
async pushToGitHub(repoName: string, commitMessage?: string, githubUsername?: string, ghToken?: string) {
438438
try {
439439
// Use cookies if username and token are not provided
440440
const githubToken = ghToken || Cookies.get('githubToken');
@@ -523,7 +523,7 @@ export class WorkbenchStore {
523523
const { data: newCommit } = await octokit.git.createCommit({
524524
owner: repo.owner.login,
525525
repo: repo.name,
526-
message: 'Initial commit from your app',
526+
message: commitMessage || 'Initial commit from your app',
527527
tree: newTree.sha,
528528
parents: [latestCommitSha],
529529
});

0 commit comments

Comments
 (0)