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
4 changes: 3 additions & 1 deletion electron/ConfigHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface Config {
debuggingModel: string;
language: string;
opacity: number;
questionType?: "coding" | "mcq" | "general";
}

export class ConfigHelper extends EventEmitter {
Expand All @@ -24,7 +25,8 @@ export class ConfigHelper extends EventEmitter {
solutionModel: "gemini-2.0-flash",
debuggingModel: "gemini-2.0-flash",
language: "python",
opacity: 1.0
opacity: 1.0,
questionType: "coding"
};

constructor() {
Expand Down
540 changes: 439 additions & 101 deletions electron/ProcessingHelper.ts

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions electron/ipcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,15 @@ export function initializeIpcHandlers(deps: IIpcHandlerDeps): void {
return { success: false, error: "Failed to delete last screenshot" }
}
})

// Follow-up chat handler
ipcMain.handle("chat-followup", async (_event, args: { message: string; context?: { solution?: string } }) => {
try {
const problemInfo = deps.getMainWindow ? (deps as any).processingHelper?.["deps"]?.getProblemInfo?.() : undefined
const result = await deps.processingHelper.generateFollowUpResponse(args.message, { problemInfo, solution: args.context?.solution || "" });
return result;
} catch (error: any) {
return { success: false, error: error?.message || "Failed to process follow-up" };
}
})
}
5 changes: 4 additions & 1 deletion electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ const electronAPI = {

// New methods for OpenAI API integration
getConfig: () => ipcRenderer.invoke("get-config"),
updateConfig: (config: { apiKey?: string; model?: string; language?: string; opacity?: number }) =>
updateConfig: (config: { apiKey?: string; model?: string; language?: string; opacity?: number; questionType?: "coding" | "mcq" | "general" }) =>
ipcRenderer.invoke("update-config", config),
onShowSettings: (callback: () => void) => {
const subscription = () => callback()
Expand Down Expand Up @@ -237,6 +237,9 @@ const electronAPI = {
}
},
deleteLastScreenshot: () => ipcRenderer.invoke("delete-last-screenshot")
,
// Chat follow-up
chatFollowup: (message: string, context?: { solution?: string }) => ipcRenderer.invoke("chat-followup", { message, context })
}

// Before exposing the API
Expand Down
61 changes: 61 additions & 0 deletions src/_pages/Solutions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ const Solutions: React.FC<SolutionsProps> = ({
null
)

// Chat state
const [chatInput, setChatInput] = useState("")
const [chatMessages, setChatMessages] = useState<Array<{ role: "user" | "assistant"; text: string }>>([])
const [isChatting, setIsChatting] = useState(false)

const [isTooltipVisible, setIsTooltipVisible] = useState(false)
const [tooltipHeight, setTooltipHeight] = useState(0)

Expand Down Expand Up @@ -557,6 +562,62 @@ const Solutions: React.FC<SolutionsProps> = ({
spaceComplexity={spaceComplexityData}
isLoading={!timeComplexityData || !spaceComplexityData}
/>

{/* Chat Panel */}
<div className="mt-4 p-3 rounded-md border border-white/10 bg-black/40">
<div className="text-[13px] font-medium text-white mb-2">Chat about this answer</div>
<div className="space-y-2 max-h-48 overflow-auto pr-1">
{chatMessages.map((m, idx) => (
<div key={idx} className="text-[12px]">
<span className={`mr-2 ${m.role === 'user' ? 'text-blue-300' : 'text-green-300'}`}>
{m.role === 'user' ? 'You' : 'AI'}:
</span>
<span className="text-white/90 whitespace-pre-wrap">{m.text}</span>
</div>
))}
{chatMessages.length === 0 && (
<div className="text-[12px] text-white/60">Ask follow-up questions about the solution.</div>
)}
</div>
<div className="mt-2 flex gap-2">
<input
value={chatInput}
onChange={(e) => setChatInput(e.target.value)}
placeholder="Ask a follow-up question..."
className="flex-1 bg-black/50 border border-white/10 rounded px-2 py-1 text-[12px] text-white outline-none focus:border-white/20"
/>
<button
disabled={isChatting || !chatInput.trim()}
onClick={async () => {
const msg = chatInput.trim()
if (!msg) return
setChatInput("")
setChatMessages((prev) => [...prev, { role: 'user', text: msg }])
setIsChatting(true)
try {
const result = await window.electronAPI.chatFollowup(msg, { solution: solutionData || '' })
if (result?.success && result.data) {
const aiText = result.data.text || ''
setChatMessages((prev) => [...prev, { role: 'assistant', text: aiText }])
// If updated code returned, update the visible solution
if (result.data.updatedCode && typeof result.data.updatedCode === 'string') {
setSolutionData(result.data.updatedCode)
}
} else {
setChatMessages((prev) => [...prev, { role: 'assistant', text: result?.error || 'Failed to get response' }])
}
} catch (err) {
setChatMessages((prev) => [...prev, { role: 'assistant', text: 'Error while contacting AI' }])
} finally {
setIsChatting(false)
}
}}
className={`px-3 py-1 rounded text-[12px] ${isChatting ? 'bg-white/10 text-white/50' : 'bg-white text-black hover:bg-white/90'}`}
>
{isChatting ? 'Sending...' : 'Send'}
</button>
</div>
</div>
</>
)}
</div>
Expand Down
47 changes: 46 additions & 1 deletion src/components/Queue/QueueCommands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const QueueCommands: React.FC<QueueCommandsProps> = ({
const [isTooltipVisible, setIsTooltipVisible] = useState(false)
const tooltipRef = useRef<HTMLDivElement>(null)
const { showToast } = useToast()
const [questionType, setQuestionType] = useState<"coding" | "mcq" | "general">("coding")

// Extract the repeated language selection logic into a separate function
const extractLanguagesAndUpdate = (direction?: 'next' | 'prev') => {
Expand Down Expand Up @@ -82,6 +83,22 @@ const QueueCommands: React.FC<QueueCommandsProps> = ({
onTooltipVisibilityChange(isTooltipVisible, tooltipHeight)
}, [isTooltipVisible])

// Load initial question type from config
useEffect(() => {
let mounted = true
window.electronAPI.getConfig()
.then((cfg: any) => {
if (!mounted) return
if (cfg && (cfg.questionType === "coding" || cfg.questionType === "mcq" || cfg.questionType === "general")) {
setQuestionType(cfg.questionType)
}
})
.catch(() => {})
return () => {
mounted = false
}
}, [])

const handleSignOut = async () => {
try {
// Clear any local storage or electron-specific data
Expand Down Expand Up @@ -430,6 +447,34 @@ const QueueCommands: React.FC<QueueCommandsProps> = ({

{/* Separator and Log Out */}
<div className="pt-3 mt-3 border-t border-white/10">
{/* Question Mode Selector */}
<div className="mb-3 px-2">
<div className="text-[11px] text-white/70 mb-1">Question Mode</div>
<div className="grid grid-cols-3 gap-2">
{([
{ key: "coding", label: "Coding" },
{ key: "mcq", label: "MCQ" },
{ key: "general", label: "General" }
] as const).map(({ key, label }) => (
<button
key={key}
className={`px-2 py-1 rounded text-[11px] transition-colors ${
questionType === key
? "bg-white/15 text-white border border-white/20"
: "bg-white/5 text-white/80 border border-white/10 hover:bg-white/10"
}`}
onClick={() => {
setQuestionType(key)
window.electronAPI.updateConfig({ questionType: key })
.catch((err: unknown) => console.error("Failed to save question type:", err))
}}
>
{label}
</button>
))}
</div>
</div>

{/* Simplified Language Selector */}
<div className="mb-3 px-2">
<div
Expand Down Expand Up @@ -459,7 +504,7 @@ const QueueCommands: React.FC<QueueCommandsProps> = ({
{/* API Key Settings */}
<div className="mb-3 px-2 space-y-1">
<div className="flex items-center justify-between text-[13px] font-medium text-white/90">
<span>OpenAI API Settings</span>
<span>API Settings</span>
<button
className="bg-white/10 hover:bg-white/20 px-2 py-1 rounded text-[11px]"
onClick={() => window.electronAPI.openSettingsPortal()}
Expand Down
56 changes: 54 additions & 2 deletions src/components/Settings/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
const [extractionModel, setExtractionModel] = useState("gpt-4o");
const [solutionModel, setSolutionModel] = useState("gpt-4o");
const [debuggingModel, setDebuggingModel] = useState("gpt-4o");
const [questionType, setQuestionType] = useState<"coding" | "mcq" | "general">("coding");
const [isLoading, setIsLoading] = useState(false);
const { showToast } = useToast();

Expand Down Expand Up @@ -213,6 +214,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
extractionModel?: string;
solutionModel?: string;
debuggingModel?: string;
questionType?: "coding" | "mcq" | "general";
}

window.electronAPI
Expand All @@ -223,6 +225,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
setExtractionModel(config.extractionModel || "gpt-4o");
setSolutionModel(config.solutionModel || "gpt-4o");
setDebuggingModel(config.debuggingModel || "gpt-4o");
setQuestionType((config.questionType as any) || "coding");
})
.catch((error: unknown) => {
console.error("Failed to load config:", error);
Expand Down Expand Up @@ -263,6 +266,7 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
extractionModel,
solutionModel,
debuggingModel,
questionType,
});

if (result) {
Expand Down Expand Up @@ -316,12 +320,60 @@ export function SettingsDialog({ open: externalOpen, onOpenChange }: SettingsDia
}}
>
<DialogHeader>
<DialogTitle>API Settings</DialogTitle>
<DialogTitle>Settings</DialogTitle>
<DialogDescription className="text-white/70">
Configure your API key and model preferences. You'll need your own API key to use this application.
Configure question type and API settings. You'll need your own API key to use this application.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Question Type Selection */}
<div className="space-y-2">
<label className="text-sm font-medium text-white">Question Type</label>
<div className="flex gap-2">
{([
{ key: "coding", label: "Coding" },
{ key: "mcq", label: "MCQ" },
{ key: "general", label: "General" },
] as const).map(({ key, label }) => (
<div
key={key}
className={`flex-1 p-2 rounded-lg cursor-pointer transition-colors ${
questionType === key
? "bg-white/10 border border-white/20"
: "bg-black/30 border border-white/5 hover:bg-white/5"
}`}
onClick={() => setQuestionType(key)}
>
<div className="flex items-center gap-2">
<div
className={`w-3 h-3 rounded-full ${
questionType === key ? "bg-white" : "bg-white/20"
}`}
/>
<div className="flex flex-col">
<p className="font-medium text-white text-sm">{label}</p>
<p className="text-xs text-white/60">
{key === "coding" && "Generate code solution"}
{key === "mcq" && "Analyze options and answer"}
{key === "general" && "Provide direct answer + reasoning"}
</p>
</div>
</div>
</div>
))}
</div>
</div>

{/* Divider */}
<div className="h-px w-full bg-white/10 my-2" />

{/* API Settings Section Label */}
<div>
<p className="text-sm font-medium text-white mb-1">API Settings</p>
<p className="text-xs text-white/60 -mt-1 mb-2">
Set your provider, API key, and preferred models
</p>
</div>
{/* API Provider Selection */}
<div className="space-y-2">
<label className="text-sm font-medium text-white">API Provider</label>
Expand Down