Refactor Study Desk application structure

This commit is contained in:
Elijah 2026-08-07 19:31:23 -07:00
parent faaccf8a7e
commit 089439ed90
145 changed files with 8087 additions and 3412 deletions

View file

@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback } from "react";
export function GenerateTab({ importType }: { importType: "flashcards" | "quizzes" | "connections" | "crossword" }) {
const [instructions, setInstructions] = useState("");
@ -9,19 +9,39 @@ export function GenerateTab({ importType }: { importType: "flashcards" | "quizze
const [saveResult, setSaveResult] = useState<"success" | "error" | null>(null);
const [copied, setCopied] = useState(false);
const [packCount, setPackCount] = useState(1);
const [loadError, setLoadError] = useState<string | null>(null);
const batchOverride = importType === "connections" && packCount > 1
? `TEMPORARY BATCH OVERRIDE:\nGenerate exactly ${packCount} distinct Connections packs. Return one raw JSON array containing exactly ${packCount} objects that each follow the schema below. Make the packs meaningfully different from one another. This override takes precedence over any later instruction to return one object.\n\n`
: "";
const displayedInstructions = `${batchOverride}${instructions}`;
useEffect(() => {
fetch(`/api/settings/llm-instructions?type=${importType}`)
.then((res) => res.json())
.then((data) => setInstructions(data.value))
.finally(() => setLoading(false));
const loadInstructions = useCallback(async (signal?: AbortSignal) => {
setLoading(true);
setLoadError(null);
try {
const response = await fetch(`/api/settings/llm-instructions?type=${importType}`, { signal });
if (!response.ok) throw new Error("Instructions could not be loaded.");
const data = await response.json() as { value?: unknown };
if (typeof data.value !== "string") throw new Error("The server returned invalid instructions.");
setInstructions(data.value);
} catch (error) {
if (signal?.aborted) return;
setLoadError(error instanceof Error ? error.message : "Instructions could not be loaded.");
} finally {
if (!signal?.aborted) setLoading(false);
}
}, [importType]);
useEffect(() => {
const controller = new AbortController();
const timer = window.setTimeout(() => void loadInstructions(controller.signal), 0);
return () => {
window.clearTimeout(timer);
controller.abort();
};
}, [loadInstructions]);
async function handleSave() {
setSaving(true);
setSaveResult(null);
@ -50,25 +70,44 @@ export function GenerateTab({ importType }: { importType: "flashcards" | "quizze
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ value: "__RESET__" }),
});
if (!res.ok) throw new Error("Failed to reset instructions");
const data = await res.json();
if (typeof data.value !== "string") throw new Error("Invalid reset response");
setInstructions(data.value);
setSaveResult("success");
} catch {
setSaveResult("error");
} finally {
setSaving(false);
}
}
async function handleCopy() {
await navigator.clipboard.writeText(displayedInstructions);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
try {
await navigator.clipboard.writeText(displayedInstructions);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
setLoadError("Clipboard access failed. Select and copy the instructions manually.");
}
}
if (loading) {
return <div className="animate-subtle-pulse text-text-muted">Loading instructions...</div>;
}
if (loadError && !instructions) {
return (
<div role="alert" className="rounded-xl border border-error/30 bg-error-bg p-4 text-error">
<p>{loadError}</p>
<button onClick={() => void loadInstructions()} className="mt-3 min-h-10 rounded-lg border border-error/40 px-4 text-sm font-bold">Retry</button>
</div>
);
}
return (
<div className="space-y-4">
{loadError && <p role="alert" className="rounded-lg bg-error-bg p-3 text-sm text-error">{loadError}</p>}
<p className="text-sm text-text-secondary">
Copy these instructions and paste them into an LLM chat along with your study material. The LLM will generate JSON you can paste into the Import tab.
</p>