Add open ai saving

fix/ai-roadmap
Kamran Ahmed 9 months ago
parent 612e750370
commit 943def6d7c
  1. 10
      src/components/GenerateRoadmap/GenerateRoadmap.tsx
  2. 14
      src/components/GenerateRoadmap/OpenAISettings.tsx
  3. 6
      src/components/GenerateRoadmap/RoadmapSearch.tsx
  4. 31
      src/components/GenerateRoadmap/RoadmapTopicDetail.tsx
  5. 6
      src/lib/jwt.ts

@ -13,7 +13,7 @@ import { renderFlowJSON } from '../../../editor/renderer/renderer';
import { replaceChildren } from '../../lib/dom'; import { replaceChildren } from '../../lib/dom';
import { readAIRoadmapStream } from '../../helper/read-stream'; import { readAIRoadmapStream } from '../../helper/read-stream';
import { import {
getOpenAPIKey, getOpenAIKey,
isLoggedIn, isLoggedIn,
removeAuthToken, removeAuthToken,
visitAIRoadmap, visitAIRoadmap,
@ -103,7 +103,7 @@ export function GenerateRoadmap() {
const [roadmapTopicLimitUsed, setRoadmapTopicLimitUsed] = useState(0); const [roadmapTopicLimitUsed, setRoadmapTopicLimitUsed] = useState(0);
const [isConfiguring, setIsConfiguring] = useState(false); const [isConfiguring, setIsConfiguring] = useState(false);
const openAPIKey = getOpenAPIKey(); const openAPIKey = getOpenAIKey();
const renderRoadmap = async (roadmap: string) => { const renderRoadmap = async (roadmap: string) => {
const { nodes, edges } = generateAIRoadmapFromText(roadmap); const { nodes, edges } = generateAIRoadmapFromText(roadmap);
@ -401,6 +401,10 @@ export function GenerateRoadmap() {
nodeType={selectedNode.nodeType} nodeType={selectedNode.nodeType}
nodeTitle={selectedNode.nodeTitle} nodeTitle={selectedNode.nodeTitle}
parentTitle={selectedNode.parentTitle} parentTitle={selectedNode.parentTitle}
onConfigureOpenAI={() => {
setSelectedNode(null);
setIsConfiguring(true);
}}
onClose={() => { onClose={() => {
setSelectedNode(null); setSelectedNode(null);
loadAIRoadmapLimit().finally(() => {}); loadAIRoadmapLimit().finally(() => {});
@ -470,7 +474,7 @@ export function GenerateRoadmap() {
className="flex flex-row items-center gap-1 rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white" className="flex flex-row items-center gap-1 rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white"
> >
<Cog size={15} /> <Cog size={15} />
Configure OpenAI API key Configure OpenAI key
</button> </button>
)} )}
</div> </div>

@ -1,9 +1,9 @@
import { Modal } from '../Modal.tsx'; import { Modal } from '../Modal.tsx';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { import {
deleteOpenAPIKey, deleteOpenAIKey,
getOpenAPIKey, getOpenAIKey,
saveOpenAPIKey, saveOpenAIKey,
} from '../../lib/jwt.ts'; } from '../../lib/jwt.ts';
import { cn } from '../../lib/classname.ts'; import { cn } from '../../lib/classname.ts';
import { CloseIcon } from '../ReactIcons/CloseIcon.tsx'; import { CloseIcon } from '../ReactIcons/CloseIcon.tsx';
@ -26,7 +26,7 @@ export function OpenAISettings(props: OpenAISettingsProps) {
const toast = useToast(); const toast = useToast();
useEffect(() => { useEffect(() => {
const apiKey = getOpenAPIKey(); const apiKey = getOpenAIKey();
setOpenaiApiKey(apiKey || ''); setOpenaiApiKey(apiKey || '');
setDefaultOpenAIKey(apiKey || ''); setDefaultOpenAIKey(apiKey || '');
}, []); }, []);
@ -59,7 +59,7 @@ export function OpenAISettings(props: OpenAISettingsProps) {
const normalizedKey = openaiApiKey.trim(); const normalizedKey = openaiApiKey.trim();
if (!normalizedKey) { if (!normalizedKey) {
deleteOpenAPIKey(); deleteOpenAIKey();
toast.success('OpenAI API key removed'); toast.success('OpenAI API key removed');
onClose(); onClose();
return; return;
@ -85,7 +85,7 @@ export function OpenAISettings(props: OpenAISettingsProps) {
} }
// Save the API key to cookies // Save the API key to cookies
saveOpenAPIKey(normalizedKey); saveOpenAIKey(normalizedKey);
toast.success('OpenAI API key saved'); toast.success('OpenAI API key saved');
onClose(); onClose();
}} }}
@ -151,7 +151,7 @@ export function OpenAISettings(props: OpenAISettingsProps) {
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
deleteOpenAPIKey(); deleteOpenAIKey();
onClose(); onClose();
toast.success('OpenAI API key removed'); toast.success('OpenAI API key removed');
}} }}

@ -7,7 +7,7 @@ import {
Wand, Wand,
} from 'lucide-react'; } from 'lucide-react';
import type { FormEvent } from 'react'; import type { FormEvent } from 'react';
import { getOpenAPIKey, isLoggedIn } from '../../lib/jwt'; import { getOpenAIKey, isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
import { cn } from '../../lib/classname.ts'; import { cn } from '../../lib/classname.ts';
import { useState } from 'react'; import { useState } from 'react';
@ -36,7 +36,7 @@ export function RoadmapSearch(props: RoadmapSearchProps) {
const canGenerateMore = limitUsed < limit; const canGenerateMore = limitUsed < limit;
const [isConfiguring, setIsConfiguring] = useState(false); const [isConfiguring, setIsConfiguring] = useState(false);
const openAPIKey = getOpenAPIKey(); const openAPIKey = getOpenAIKey();
const randomTerms = ['OAuth', 'APIs', 'UX Design', 'gRPC']; const randomTerms = ['OAuth', 'APIs', 'UX Design', 'gRPC'];
@ -178,7 +178,7 @@ export function RoadmapSearch(props: RoadmapSearchProps) {
className="flex flex-row items-center gap-1 rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white" className="flex flex-row items-center gap-1 rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white"
> >
<Cog size={15} /> <Cog size={15} />
Configure OpenAI API key Configure OpenAI key
</button> </button>
)} )}
</p> </p>

@ -3,13 +3,14 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { useKeydown } from '../../hooks/use-keydown'; import { useKeydown } from '../../hooks/use-keydown';
import { useOutsideClick } from '../../hooks/use-outside-click'; import { useOutsideClick } from '../../hooks/use-outside-click';
import { markdownToHtml } from '../../lib/markdown'; import { markdownToHtml } from '../../lib/markdown';
import { Ban, FileText, X } from 'lucide-react'; import { Ban, Cog, FileText, X } from 'lucide-react';
import { Spinner } from '../ReactIcons/Spinner'; import { Spinner } from '../ReactIcons/Spinner';
import type { RoadmapNodeDetails } from './GenerateRoadmap'; import type { RoadmapNodeDetails } from './GenerateRoadmap';
import { isLoggedIn, removeAuthToken } from '../../lib/jwt'; import { getOpenAIKey, isLoggedIn, removeAuthToken } from '../../lib/jwt';
import { readAIRoadmapContentStream } from '../../helper/read-stream'; import { readAIRoadmapContentStream } from '../../helper/read-stream';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
import { OpenAISettings } from './OpenAISettings.tsx';
type RoadmapTopicDetailProps = RoadmapNodeDetails & { type RoadmapTopicDetailProps = RoadmapNodeDetails & {
onClose?: () => void; onClose?: () => void;
@ -17,6 +18,7 @@ type RoadmapTopicDetailProps = RoadmapNodeDetails & {
topicLimitUsed: number; topicLimitUsed: number;
topicLimit: number; topicLimit: number;
onTopicContentGenerateComplete?: () => void; onTopicContentGenerateComplete?: () => void;
onConfigureOpenAI?: () => void;
}; };
export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) { export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) {
@ -28,6 +30,7 @@ export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) {
topicLimit, topicLimit,
topicLimitUsed, topicLimitUsed,
onTopicContentGenerateComplete, onTopicContentGenerateComplete,
onConfigureOpenAI,
} = props; } = props;
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -121,6 +124,7 @@ export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) {
}, []); }, []);
const hasContent = topicHtml?.length > 0; const hasContent = topicHtml?.length > 0;
const openAIKey = getOpenAIKey();
return ( return (
<div className={'relative z-50'}> <div className={'relative z-50'}>
@ -129,7 +133,7 @@ export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) {
tabIndex={0} tabIndex={0}
className="fixed right-0 top-0 z-40 h-screen w-full overflow-y-auto bg-white p-4 focus:outline-0 sm:max-w-[600px] sm:p-6" className="fixed right-0 top-0 z-40 h-screen w-full overflow-y-auto bg-white p-4 focus:outline-0 sm:max-w-[600px] sm:p-6"
> >
<div className="flex flex-col sm:flex-row items-start gap-2"> <div className="flex flex-col items-start gap-2 sm:flex-row">
<span> <span>
<span <span
className={cn( className={cn(
@ -149,8 +153,25 @@ export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) {
className="rounded-xl border border-current px-1.5 py-0.5 text-left text-sm font-medium text-blue-500 sm:text-center" className="rounded-xl border border-current px-1.5 py-0.5 text-left text-sm font-medium text-blue-500 sm:text-center"
onClick={showLoginPopup} onClick={showLoginPopup}
> >
Generate more by{' '} Generate more by <span className="font-semibold">logging in</span>
<span className="font-semibold">logging in</span> </button>
)}
{isLoggedIn() && !openAIKey && (
<button
className="rounded-xl border border-current px-1.5 py-0.5 text-left text-sm font-medium text-blue-500 sm:text-center"
onClick={onConfigureOpenAI}
>
By-pass all limits by{' '}
<span className="font-semibold">adding your own OpenAI Key</span>
</button>
)}
{isLoggedIn() && openAIKey && (
<button
className="flex items-center gap-1 rounded-xl border border-current px-1.5 py-0.5 text-left text-sm font-medium text-blue-500 sm:text-center"
onClick={onConfigureOpenAI}
>
<Cog className="-mt-0.5 inline-block h-4 w-4" />
Configure OpenAI Key
</button> </button>
)} )}
</div> </div>

@ -64,14 +64,14 @@ export function visitAIRoadmap(roadmapId: string) {
}); });
} }
export function deleteOpenAPIKey() { export function deleteOpenAIKey() {
Cookies.remove('oak', { Cookies.remove('oak', {
path: '/', path: '/',
domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh', domain: import.meta.env.DEV ? 'localhost' : '.roadmap.sh',
}); });
} }
export function saveOpenAPIKey(apiKey: string) { export function saveOpenAIKey(apiKey: string) {
Cookies.set('oak', apiKey, { Cookies.set('oak', apiKey, {
path: '/', path: '/',
expires: 365, expires: 365,
@ -81,6 +81,6 @@ export function saveOpenAPIKey(apiKey: string) {
}); });
} }
export function getOpenAPIKey() { export function getOpenAIKey() {
return Cookies.get('oak'); return Cookies.get('oak');
} }

Loading…
Cancel
Save