Skip to content
Open
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
54 changes: 37 additions & 17 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,44 @@ import React from 'react'
import Show from './components/Show'
import { Button } from './components/ui/button'
import { Input } from './components/ui/input'
import { FormDescription } from './components/ui/form'
import { toast } from 'sonner'

const Popup: React.FC = () => {
const [openAIKey, setOpenAIKey] = React.useState('')
const [isLoaded, setIsLoaded] = React.useState(false)
const [isSaving, setIsSaving] = React.useState(false)

React.useEffect(() => {
;(async function loadOpenAPIKey() {
if (!chrome) return
const apiKeyFromStorage = (await chrome.storage.local.get('apiKey')) as {
apiKey?: string;
};
apiKey?: string
}
if (apiKeyFromStorage.apiKey)
setOpenAIKey(`${apiKeyFromStorage.apiKey.substring(0, 12)}-XXXXXX`);
setIsLoaded(true);
})();
}, []);
setOpenAIKey(`${apiKeyFromStorage.apiKey.substring(0, 12)}-XXXXXX`)
setIsLoaded(true)
})()
}, [])

const handleAddOpenAPIKey = async () => {
if (openAIKey) {
await chrome.storage.local.set({ apiKey: openAIKey })
}
if (!openAIKey) return
setIsSaving(true) // Start loading animation
await chrome.storage.local.set({ apiKey: openAIKey })

toast.success('API Key saved successfully!') // Show toast notification

// Close the popup and refresh the page
setTimeout(() => {
chrome.runtime.reload() // Reloads the extension
window.close() // Closes the popup
}, 1500)
}

return (
<div className="dark relative w-[350px] bg-[#121627] p-4 text-black">
<div className="dark relative w-[350px] bg-[#121627] p-4 text-black">
<Show show={isLoaded}>
<div className="">
<div className="w-full h-20 overflow-hidden ">
<div className="w-full h-20 overflow-hidden">
<img
className="mx-auto h-20 w-auto"
src={leetCode}
Expand All @@ -40,7 +49,7 @@ const Popup: React.FC = () => {
/>
</div>
<div className="text-center">
<h1 className=" font-bold text-3xl text-white">
<h1 className="font-bold text-3xl text-white">
LeetCode <span className="text-whisperOrange">Whisper</span>
</h1>
<p className="text-base text-slate-400">
Expand All @@ -57,18 +66,29 @@ const Popup: React.FC = () => {
placeholder="Ex. 0aBbnGgzXXXXXX"
className="bg-white text-black outline-none"
/>

<Button onClick={handleAddOpenAPIKey} className="dark">
Save
<Button
onClick={handleAddOpenAPIKey}
className="dark"
disabled={isSaving}
>
{isSaving ? (
<div className="flex items-center gap-2">
<span className="spinner-border animate-spin inline-block w-4 h-4 border-2 border-white rounded-full"></span>
Saving...
</div>
) : (
'Save'
)}
</Button>
</div>
<div className=" h-16 flex items-center justify-center">
<div className="h-16 flex items-center justify-center">
<p className="text-white text-[14px]">
Want more features?&nbsp;
<a
href="https://github.com/piyushgarg-dev/leetcode-whisper-chrome-extension"
className="text-[#86ccee]"
target="_blank"
rel="noopener noreferrer"
>
{' '}
Request a feature!
Expand Down
124 changes: 73 additions & 51 deletions src/content/content.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import React, { useRef } from 'react';
import { Button } from '@/components/ui/button';
import { Bot, ClipboardCopy, Send, SendHorizontal } from 'lucide-react';
import OpenAI from 'openai';
import React, { useEffect, useRef } from 'react'
import { Button } from '@/components/ui/button'
import { Bot, ClipboardCopy, Send, SendHorizontal } from 'lucide-react'
import OpenAI from 'openai'

import './style.css';
import { Input } from '@/components/ui/input';
import { SYSTEM_PROMPT } from '@/constants/prompt';
import { extractCode } from './util';
import { ChatCompletionMessageParam } from 'openai/resources/index.mjs';
import './style.css'
import { Input } from '@/components/ui/input'
import { SYSTEM_PROMPT } from '@/constants/prompt'
import { extractCode } from './util'
import { ChatCompletionMessageParam } from 'openai/resources/index.mjs'

import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion';
} from '@/components/ui/accordion'

import { cn } from '@/lib/utils';
import { Card, CardContent, CardFooter } from '@/components/ui/card';
import { cn } from '@/lib/utils'
import { Card, CardContent, CardFooter } from '@/components/ui/card'

function createOpenAISDK(apiKey: string) {
return new OpenAI({
Expand All @@ -27,27 +27,27 @@ function createOpenAISDK(apiKey: string) {
}

interface ChatBoxProps {
visible: boolean;
visible: boolean
context: {
problemStatement: string;
};
problemStatement: string
}
}

interface ChatMessage {
role: 'user' | 'assistant';
message: string;
type: 'text' | 'markdown';
role: 'user' | 'assistant'
message: string
type: 'text' | 'markdown'
assistantResponse?: {
feedback?: string;
hints?: string[];
snippet?: string;
programmingLanguage?: string;
};
feedback?: string
hints?: string[]
snippet?: string
programmingLanguage?: string
}
}

function ChatBox({ context, visible }: ChatBoxProps) {
const [value, setValue] = React.useState('');
const [chatHistory, setChatHistory] = React.useState<ChatMessage[]>([]);
const [value, setValue] = React.useState('')
const [chatHistory, setChatHistory] = React.useState<ChatMessage[]>([])

const chatBoxRef = useRef<HTMLDivElement>(null)

Expand All @@ -60,26 +60,26 @@ function ChatBox({ context, visible }: ChatBoxProps) {

const openai = createOpenAISDK(openAIAPIKey.apiKey)

const userMessage = value;
const userCurrentCodeContainer = document.querySelectorAll('.view-line');
const userMessage = value
const userCurrentCodeContainer = document.querySelectorAll('.view-line')
const changeLanguageButton = document.querySelector(
'button.rounded.items-center.whitespace-nowrap.inline-flex.bg-transparent.dark\\:bg-dark-transparent.text-text-secondary.group'
);
let programmingLanguage = 'UNKNOWN';
)
let programmingLanguage = 'UNKNOWN'

if (changeLanguageButton) {
if (changeLanguageButton.textContent)
programmingLanguage = changeLanguageButton.textContent;
programmingLanguage = changeLanguageButton.textContent
}

const extractedCode = extractCode(userCurrentCodeContainer);
const extractedCode = extractCode(userCurrentCodeContainer)

const systemPromptModified = SYSTEM_PROMPT.replace(
'{{problem_statement}}',
context.problemStatement
)
.replace('{{programming_language}}', programmingLanguage)
.replace('{{user_code}}', extractedCode);
.replace('{{user_code}}', extractedCode)

const apiResponse = await openai.chat.completions.create({
model: 'chatgpt-4o-latest',
Expand All @@ -101,7 +101,7 @@ function ChatBox({ context, visible }: ChatBoxProps) {
})

if (apiResponse.choices[0].message.content) {
const result = JSON.parse(apiResponse.choices[0].message.content);
const result = JSON.parse(apiResponse.choices[0].message.content)

if ('output' in result) {
setChatHistory((prev) => [
Expand All @@ -117,8 +117,8 @@ function ChatBox({ context, visible }: ChatBoxProps) {
programmingLanguage: result.output.programmingLanguage,
},
},
]);
chatBoxRef.current?.scrollIntoView({ behavior: 'smooth' });
])
chatBoxRef.current?.scrollIntoView({ behavior: 'smooth' })
}
}
}
Expand All @@ -127,13 +127,13 @@ function ChatBox({ context, visible }: ChatBoxProps) {
setChatHistory((prev) => [
...prev,
{ role: 'user', message: value, type: 'text' },
]);
chatBoxRef.current?.scrollIntoView({ behavior: 'smooth' });
setValue('');
handleGenerateAIResponse();
};
])
chatBoxRef.current?.scrollIntoView({ behavior: 'smooth' })
setValue('')
handleGenerateAIResponse()
}

if (!visible) return <></>;
if (!visible) return <></>

return (
<Card className="mb-5">
Expand Down Expand Up @@ -201,10 +201,10 @@ function ChatBox({ context, visible }: ChatBoxProps) {
<CardFooter>
<form
onSubmit={(event) => {
event.preventDefault();
if (value.length === 0) return;
onSendMessage();
setValue('');
event.preventDefault()
if (value.length === 0) return
onSendMessage()
setValue('')
}}
className="flex w-full items-center space-x-2"
>
Expand All @@ -223,24 +223,46 @@ function ChatBox({ context, visible }: ChatBoxProps) {
</form>
</CardFooter>
</Card>
);
)
}


const ContentPage: React.FC = () => {
const [openAIAPIKey, setOpenAIAPIKey] = React.useState<{ apiKey?: string }>({
apiKey: '',
})

const [chatboxExpanded, setChatboxExpanded] = React.useState(false)

const metaDescriptionEl = document.querySelector('meta[name=description]')
useEffect(() => {
chrome.storage.local.get('apiKey', (result) => {
setOpenAIAPIKey(result as { apiKey?: string })
})
}, [])

const handleSetApiKey = () => {
// Opens the extension's options page
//TODO: we can add a option page to add the api key
}

const metaDescriptionEl = document.querySelector('meta[name=description]')
const problemStatement = metaDescriptionEl?.getAttribute('content') as string

return (
<div className="__chat-container dark z-50">
<ChatBox visible={chatboxExpanded} context={{ problemStatement }} />
<div className="flex justify-end">
<Button onClick={() => setChatboxExpanded(!chatboxExpanded)}>
<Bot />
Ask AI
</Button>
{openAIAPIKey.apiKey ? (
<Button onClick={() => setChatboxExpanded(!chatboxExpanded)}>
<Bot />
Ask AI
</Button>
) : (
<Button className="cursor-not-allowed" onClick={handleSetApiKey}>
<Bot />
Set API Key to Enable AI
</Button>
)}
</div>
</div>
)
Expand Down
3 changes: 3 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { Toaster } from "@/components/ui/sonner"


createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
<Toaster position='top-center'/>
</StrictMode>
)