Skip to content

Commit c5a8b83

Browse files
Chatbot Fixes
1 parent fd1ad49 commit c5a8b83

File tree

11 files changed

+492
-55
lines changed

11 files changed

+492
-55
lines changed

src/app/(protected)/docs/page.tsx

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import React, { useState, useEffect } from 'react';
44
import { useProjectsContext } from '@/context/ProjectsContext';
5-
import { getProjectDocs, regenerateProjectDocs, modifyDocsWithQna, getDocsQnaHistory, createDocsShare, revokeDocsShare, getDocsShare, deleteDocsQnaRecord, deleteAllDocsQnaHistory } from '@/lib/actions';
5+
import { getProjectDocs, regenerateProjectDocs, modifyDocsWithQna, getDocsQnaHistory, createDocsShare, revokeDocsShare, getDocsShare, deleteDocsQnaRecord, deleteAllDocsQnaHistory, updateProjectGithubToken } from '@/lib/actions';
66
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
77
import { Button } from '@/components/ui/button';
88
import { Badge } from '@/components/ui/badge';
@@ -110,6 +110,10 @@ function DocsPage() {
110110
const [qnaToDelete, setQnaToDelete] = useState<string | null>(null);
111111
const [showDeleteAllDialog, setShowDeleteAllDialog] = useState(false);
112112
const [showProcessingNotice, setShowProcessingNotice] = useState(true);
113+
const [showTokenModal, setShowTokenModal] = useState(false);
114+
const [githubTokenInput, setGithubTokenInput] = useState('');
115+
const [isSavingToken, setIsSavingToken] = useState(false);
116+
const [tokenError, setTokenError] = useState<string | null>(null);
113117

114118
const selectedProject = projects.find(p => p.id === selectedProjectId);
115119

@@ -188,7 +192,7 @@ function DocsPage() {
188192
}
189193
};
190194

191-
const handleRegenerateDocs = async () => {
195+
const regenerateDocs = async (options?: { suppressRateLimitPrompt?: boolean }) => {
192196
if (!selectedProjectId) return;
193197

194198
setIsRegenerating(true);
@@ -208,17 +212,58 @@ function DocsPage() {
208212
const rawMessage = err instanceof Error ? err.message : 'Failed to regenerate documentation';
209213
const isRateLimit = rawMessage.toLowerCase().includes('github api rate limit exceeded');
210214
const errorMessage = isRateLimit
211-
? 'GitHub API rate limit hit(this issue is via GitHub API, not RepoDoc). Add a GitHub personal access token when creating the project to avoid this limit.'
215+
? 'GitHub API rate limit hit (this comes from GitHub). Add a GitHub personal access token with repo scope to this project to continue.'
212216
: rawMessage;
213217
setError(errorMessage);
214-
toast.error('Failed to regenerate documentation', {
218+
toast.error(isRateLimit ? 'GitHub rate limit exceeded' : 'Failed to regenerate documentation', {
215219
description: errorMessage,
216220
});
221+
if (isRateLimit && !options?.suppressRateLimitPrompt) {
222+
setGithubTokenInput('');
223+
setTokenError(null);
224+
setShowTokenModal(true);
225+
}
217226
} finally {
218227
setIsRegenerating(false);
219228
}
220229
};
221230

231+
const handleRegenerateDocs = () => {
232+
void regenerateDocs();
233+
};
234+
235+
const handleSaveGithubToken = async () => {
236+
if (!selectedProjectId) return;
237+
238+
const token = githubTokenInput.trim();
239+
if (!token) {
240+
setTokenError('Please enter your GitHub personal access token.');
241+
return;
242+
}
243+
244+
setIsSavingToken(true);
245+
setTokenError(null);
246+
247+
try {
248+
await updateProjectGithubToken(selectedProjectId, token);
249+
toast.success('GitHub token saved', {
250+
description: 'We will use this token for future indexing and regeneration.',
251+
});
252+
setShowTokenModal(false);
253+
setGithubTokenInput('');
254+
await regenerateDocs({ suppressRateLimitPrompt: true });
255+
} catch (err) {
256+
console.error('Error saving GitHub token:', err);
257+
const message = err instanceof Error ? err.message : 'Failed to save GitHub token';
258+
setTokenError(message);
259+
toast.error('Failed to save token', {
260+
description: message,
261+
});
262+
} finally {
263+
setIsSavingToken(false);
264+
}
265+
};
266+
222267
const handleQnaSubmit = async () => {
223268
if (!selectedProjectId || !qnaQuestion.trim()) return;
224269

@@ -471,7 +516,7 @@ function DocsPage() {
471516
<AlertDescription className="mt-2 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 text-sm text-blue-100/90">
472517
<span>
473518
Large repositories can take longer to index, especially on the first load. If GitHub rate limits the request,
474-
add a personal access token with <code className="font-mono">repo</code> scope when creating the project to keep things moving quickly.
519+
add a personal access token with <code className="font-mono">repo</code> scope (use “Manage GitHub Token” above) to keep things moving quickly.
475520
</span>
476521
<Button
477522
variant="ghost"
@@ -499,6 +544,17 @@ function DocsPage() {
499544
</div>
500545
</div>
501546
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:gap-3">
547+
<Button
548+
onClick={() => {
549+
setShowTokenModal(true);
550+
setGithubTokenInput('');
551+
setTokenError(null);
552+
}}
553+
variant="outline"
554+
className="border-blue-400/40 text-blue-200 hover:bg-blue-500/10 px-3 sm:px-4 md:px-6 py-2 rounded-lg transition-all duration-200 w-full sm:w-auto text-xs sm:text-sm"
555+
>
556+
Manage GitHub Token
557+
</Button>
502558
<Button
503559
onClick={() => setIsQnaPanelOpen(!isQnaPanelOpen)}
504560
variant="outline"
@@ -1019,6 +1075,75 @@ function DocsPage() {
10191075
</DialogContent>
10201076
</Dialog>
10211077

1078+
{/* GitHub Token Modal */}
1079+
<Dialog open={showTokenModal} onOpenChange={setShowTokenModal}>
1080+
<DialogContent className="bg-gray-900 border-white/20">
1081+
<DialogHeader>
1082+
<DialogTitle className="text-white">Add GitHub Personal Access Token</DialogTitle>
1083+
<DialogDescription className="text-white/60">
1084+
We use this token to fetch repository files without hitting GitHub’s rate limits. Generate a token with
1085+
<span className="font-mono text-white/80"> repo </span> scope and paste it below.
1086+
</DialogDescription>
1087+
</DialogHeader>
1088+
<div className="space-y-4">
1089+
<div className="space-y-2">
1090+
<label className="text-sm font-medium text-white/80" htmlFor="github-token-input">
1091+
Token
1092+
</label>
1093+
<Input
1094+
id="github-token-input"
1095+
type="password"
1096+
value={githubTokenInput}
1097+
onChange={(event) => setGithubTokenInput(event.target.value)}
1098+
placeholder="ghp_XXXXXXXXXXXXXXXXXXXX"
1099+
className="bg-white/5 border-white/20 text-white text-sm"
1100+
autoComplete="off"
1101+
spellCheck={false}
1102+
/>
1103+
<p className="text-xs text-white/50">
1104+
You can create a new token at{' '}
1105+
<a
1106+
href="https://github.com/settings/tokens"
1107+
target="_blank"
1108+
rel="noopener noreferrer"
1109+
className="text-blue-300 hover:underline"
1110+
>
1111+
github.com/settings/tokens
1112+
</a>
1113+
. Keep this token secret—never share it publicly.
1114+
</p>
1115+
{tokenError && <p className="text-xs text-red-400">{tokenError}</p>}
1116+
</div>
1117+
</div>
1118+
<DialogFooter>
1119+
<Button
1120+
variant="outline"
1121+
onClick={() => {
1122+
setShowTokenModal(false);
1123+
setTokenError(null);
1124+
}}
1125+
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
1126+
>
1127+
Cancel
1128+
</Button>
1129+
<Button
1130+
onClick={handleSaveGithubToken}
1131+
disabled={isSavingToken}
1132+
className="bg-blue-600 hover:bg-blue-700 text-white"
1133+
>
1134+
{isSavingToken ? (
1135+
<>
1136+
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
1137+
Saving...
1138+
</>
1139+
) : (
1140+
'Save Token'
1141+
)}
1142+
</Button>
1143+
</DialogFooter>
1144+
</DialogContent>
1145+
</Dialog>
1146+
10221147
{/* Delete All Q&A Dialog */}
10231148
<Dialog open={showDeleteAllDialog} onOpenChange={setShowDeleteAllDialog}>
10241149
<DialogContent className="bg-gray-900 border-white/20">

src/app/(protected)/readme/page.tsx

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import React, { useState, useEffect } from 'react';
44
import { useProjectsContext } from '@/context/ProjectsContext';
5-
import { getProjectReadme, regenerateProjectReadme, modifyReadmeWithQna, getReadmeQnaHistory, createReadmeShare, revokeReadmeShare, getReadmeShare, deleteReadmeQnaRecord, deleteAllReadmeQnaHistory } from '@/lib/actions';
5+
import { getProjectReadme, regenerateProjectReadme, modifyReadmeWithQna, getReadmeQnaHistory, createReadmeShare, revokeReadmeShare, getReadmeShare, deleteReadmeQnaRecord, deleteAllReadmeQnaHistory, updateProjectGithubToken } from '@/lib/actions';
66
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
77
import { Button } from '@/components/ui/button';
88
import { Badge } from '@/components/ui/badge';
@@ -95,6 +95,10 @@ function ReadmePage() {
9595
const [qnaToDelete, setQnaToDelete] = useState<string | null>(null);
9696
const [showDeleteAllDialog, setShowDeleteAllDialog] = useState(false);
9797
const [showProcessingNotice, setShowProcessingNotice] = useState(true);
98+
const [showTokenModal, setShowTokenModal] = useState(false);
99+
const [githubTokenInput, setGithubTokenInput] = useState('');
100+
const [isSavingToken, setIsSavingToken] = useState(false);
101+
const [tokenError, setTokenError] = useState<string | null>(null);
98102

99103
const selectedProject = projects.find(p => p.id === selectedProjectId);
100104

@@ -132,6 +136,38 @@ function ReadmePage() {
132136
return { title, description, stars, forks, language, license };
133137
};
134138

139+
const handleSaveGithubToken = async () => {
140+
if (!selectedProjectId) return;
141+
142+
const token = githubTokenInput.trim();
143+
if (!token) {
144+
setTokenError('Please enter your GitHub personal access token.');
145+
return;
146+
}
147+
148+
setIsSavingToken(true);
149+
setTokenError(null);
150+
151+
try {
152+
await updateProjectGithubToken(selectedProjectId, token);
153+
toast.success('GitHub token saved', {
154+
description: 'We will use this token for future indexing and regeneration.',
155+
});
156+
setShowTokenModal(false);
157+
setGithubTokenInput('');
158+
await regenerateReadme({ suppressRateLimitPrompt: true });
159+
} catch (err) {
160+
console.error('Error saving GitHub token:', err);
161+
const message = err instanceof Error ? err.message : 'Failed to save GitHub token';
162+
setTokenError(message);
163+
toast.error('Failed to save token', {
164+
description: message,
165+
});
166+
} finally {
167+
setIsSavingToken(false);
168+
}
169+
};
170+
135171
const handleCopyCode = async () => {
136172
if (!readmeData?.content) return;
137173

@@ -173,7 +209,7 @@ function ReadmePage() {
173209
}
174210
};
175211

176-
const handleRegenerateReadme = async () => {
212+
const regenerateReadme = async (options?: { suppressRateLimitPrompt?: boolean }) => {
177213
if (!selectedProjectId) return;
178214

179215
setIsRegenerating(true);
@@ -193,17 +229,26 @@ function ReadmePage() {
193229
const rawMessage = err instanceof Error ? err.message : 'Failed to regenerate README';
194230
const isRateLimit = rawMessage.toLowerCase().includes('github api rate limit exceeded');
195231
const errorMessage = isRateLimit
196-
? 'GitHub rate limit hit. Add a GitHub personal access token (with repo scope) when creating the project to avoid this limit.'
232+
? 'GitHub API rate limit hit (this comes from GitHub). Add a GitHub personal access token with repo scope to this project to continue.'
197233
: rawMessage;
198234
setError(errorMessage);
199-
toast.error('Failed to regenerate README', {
235+
toast.error(isRateLimit ? 'GitHub rate limit exceeded' : 'Failed to regenerate README', {
200236
description: errorMessage,
201237
});
238+
if (isRateLimit && !options?.suppressRateLimitPrompt) {
239+
setGithubTokenInput('');
240+
setTokenError(null);
241+
setShowTokenModal(true);
242+
}
202243
} finally {
203244
setIsRegenerating(false);
204245
}
205246
};
206247

248+
const handleRegenerateReadme = () => {
249+
void regenerateReadme();
250+
};
251+
207252
const handleQnaSubmit = async () => {
208253
if (!selectedProjectId || !qnaQuestion.trim()) return;
209254

@@ -456,7 +501,7 @@ function ReadmePage() {
456501
<AlertDescription className="mt-2 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 text-sm text-blue-100/90">
457502
<span>
458503
Large repositories can take longer to index, especially on the first load. If GitHub rate limits the request,
459-
add a personal access token with <code className="font-mono">repo</code> scope when creating the project to keep things moving quickly.
504+
add a personal access token with <code className="font-mono">repo</code> scope (use “Manage GitHub Token” above) to keep things moving quickly.
460505
</span>
461506
<Button
462507
variant="ghost"
@@ -484,6 +529,17 @@ function ReadmePage() {
484529
</div>
485530
</div>
486531
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:gap-3">
532+
<Button
533+
onClick={() => {
534+
setShowTokenModal(true);
535+
setGithubTokenInput('');
536+
setTokenError(null);
537+
}}
538+
variant="outline"
539+
className="border-blue-400/40 text-blue-200 hover:bg-blue-500/10 px-3 sm:px-4 md:px-6 py-2 rounded-lg transition-all duration-200 w-full sm:w-auto text-xs sm:text-sm"
540+
>
541+
Manage GitHub Token
542+
</Button>
487543
{shareToken ? (
488544
<Button
489545
onClick={() => setShowShareModal(true)}
@@ -997,6 +1053,75 @@ function ReadmePage() {
9971053
</div>
9981054
)}
9991055

1056+
{/* GitHub Token Modal */}
1057+
<Dialog open={showTokenModal} onOpenChange={setShowTokenModal}>
1058+
<DialogContent className="bg-gray-900 border-white/20">
1059+
<DialogHeader>
1060+
<DialogTitle className="text-white">Add GitHub Personal Access Token</DialogTitle>
1061+
<DialogDescription className="text-white/60">
1062+
We use this token to fetch repository files without hitting GitHub’s rate limits. Generate a token with
1063+
<span className="font-mono text-white/80"> repo </span> scope and paste it below.
1064+
</DialogDescription>
1065+
</DialogHeader>
1066+
<div className="space-y-4">
1067+
<div className="space-y-2">
1068+
<label className="text-sm font-medium text-white/80" htmlFor="github-token-input-readme">
1069+
Token
1070+
</label>
1071+
<Input
1072+
id="github-token-input-readme"
1073+
type="password"
1074+
value={githubTokenInput}
1075+
onChange={(event) => setGithubTokenInput(event.target.value)}
1076+
placeholder="ghp_XXXXXXXXXXXXXXXXXXXX"
1077+
className="bg-white/5 border-white/20 text-white text-sm"
1078+
autoComplete="off"
1079+
spellCheck={false}
1080+
/>
1081+
<p className="text-xs text-white/50">
1082+
You can create a new token at{' '}
1083+
<a
1084+
href="https://github.com/settings/tokens"
1085+
target="_blank"
1086+
rel="noopener noreferrer"
1087+
className="text-blue-300 hover:underline"
1088+
>
1089+
github.com/settings/tokens
1090+
</a>
1091+
. Keep this token secret—never share it publicly.
1092+
</p>
1093+
{tokenError && <p className="text-xs text-red-400">{tokenError}</p>}
1094+
</div>
1095+
</div>
1096+
<DialogFooter>
1097+
<Button
1098+
variant="outline"
1099+
onClick={() => {
1100+
setShowTokenModal(false);
1101+
setTokenError(null);
1102+
}}
1103+
className="bg-white/10 hover:bg-white/20 text-white border-white/20"
1104+
>
1105+
Cancel
1106+
</Button>
1107+
<Button
1108+
onClick={handleSaveGithubToken}
1109+
disabled={isSavingToken}
1110+
className="bg-blue-600 hover:bg-blue-700 text-white"
1111+
>
1112+
{isSavingToken ? (
1113+
<>
1114+
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
1115+
Saving...
1116+
</>
1117+
) : (
1118+
'Save Token'
1119+
)}
1120+
</Button>
1121+
</DialogFooter>
1122+
</DialogContent>
1123+
</Dialog>
1124+
10001125
{/* Delete Single Q&A Dialog */}
10011126
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
10021127
<DialogContent className="bg-gray-900 border-white/20">

0 commit comments

Comments
 (0)