Skip to content

Commit 7aa6aed

Browse files
committed
Let user try again on failure
1 parent 789082e commit 7aa6aed

5 files changed

Lines changed: 97 additions & 22 deletions

File tree

‎apps/backend/src/problem-actions/src/generate-problem-text.ts‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ export async function generateProblemText(
1010
env: Env,
1111
forceError?: boolean,
1212
returnDummy?: boolean,
13-
baseProblem?: { problemText: string; direction: "easier" | "harder" },
13+
baseProblem?: {
14+
problemText: string;
15+
direction: "easier" | "harder" | "similar";
16+
},
1417
) {
1518
if (forceError) {
1619
throw new Error("Force error: generateObject call skipped");
@@ -36,7 +39,29 @@ export async function generateProblemText(
3639
// Build prompt based on whether we're creating a new problem or adjusting difficulty
3740
let prompt: string;
3841
if (baseProblem) {
39-
prompt = `You are adjusting the difficulty of an existing coding problem.
42+
if (baseProblem.direction === "similar") {
43+
prompt = `You are regenerating a coding problem with the same difficulty level but more specific to avoid generation errors.
44+
45+
Original problem:
46+
${baseProblem.problemText}
47+
48+
Create a problem with the same difficulty level, but be more specific and clear to avoid generation errors. Keep the same general concept/theme.
49+
- Be more explicit about constraints and requirements
50+
- Clarify edge cases and input/output formats
51+
- Ensure the problem description is unambiguous and specific
52+
- Maintain the same algorithmic complexity and difficulty
53+
54+
ONLY return the problem text, no other text.
55+
DO NOT INCLUDE TEST CASES. JUST THE PROBLEM TEXT.
56+
DO NOT INCLUDE EXAMPLE INPUTS AND OUTPUTS.
57+
DO NOT INCLUDE ANYTHING BUT THE PROBLEM TEXT.
58+
Generate a function signature for the function using TypeScript types.
59+
If using custom types, THEY MUST BE DEFINED INLINE -- for example,
60+
61+
(nums: number[], k: number, customType: {something: string; anotherThing: number}): number
62+
`;
63+
} else {
64+
prompt = `You are adjusting the difficulty of an existing coding problem.
4065
4166
Original problem:
4267
${baseProblem.problemText}
@@ -53,6 +78,7 @@ If using custom types, THEY MUST BE DEFINED INLINE -- for example,
5378
5479
(nums: number[], k: number, customType: {something: string; anotherThing: number}): number
5580
`;
81+
}
5682
} else {
5783
prompt = `Generate a coding problem for a LeetCode-style platform. ONLY return the problem text, no other text.
5884
DO NOT INCLUDE TEST CASES. JUST THE PROBLEM TEXT.

‎apps/backend/src/routes/problems.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,10 @@ async function startWorkflowIfAuto(
122122
model?: string,
123123
autoGenerate: boolean = true,
124124
returnDummy?: boolean,
125-
baseProblem?: { problemText: string; direction: "easier" | "harder" },
125+
baseProblem?: {
126+
problemText: string;
127+
direction: "easier" | "harder" | "similar";
128+
},
126129
): Promise<string | null> {
127130
if (!autoGenerate) return null;
128131

@@ -250,7 +253,7 @@ problems.openapi(createProblemRoute, async (c) => {
250253

251254
// Handle difficulty adjustment if startFrom is provided
252255
let baseProblem:
253-
| { problemText: string; direction: "easier" | "harder" }
256+
| { problemText: string; direction: "easier" | "harder" | "similar" }
254257
| undefined;
255258
const problemCreateData: {
256259
generatedByUserId: string;
@@ -268,14 +271,15 @@ problems.openapi(createProblemRoute, async (c) => {
268271
direction: body.startFrom.direction,
269272
};
270273

271-
// Set the FK based on direction
274+
// Set the FK based on direction (only for easier/harder, not for similar)
272275
if (body.startFrom.direction === "harder") {
273276
// New problem is harder, so the original is easier
274277
problemCreateData.easierThan = body.startFrom.problemId;
275-
} else {
278+
} else if (body.startFrom.direction === "easier") {
276279
// New problem is easier, so the original is harder
277280
problemCreateData.harderThan = body.startFrom.problemId;
278281
}
282+
// For "similar" direction, we don't set any FK relationships
279283
}
280284

281285
const problemId = await createProblem(problemCreateData);

‎apps/backend/src/workflows/problem-generation.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ type WorkflowParams = {
2424
model: string;
2525
returnDummy?: boolean;
2626
startingStep?: GenerationStep; // Optional: skip steps before this one
27-
baseProblem?: { problemText: string; direction: "easier" | "harder" }; // For difficulty adjustment
27+
baseProblem?: {
28+
problemText: string;
29+
direction: "easier" | "harder" | "similar";
30+
}; // For difficulty adjustment or regeneration
2831
};
2932

3033
export class ProblemGenerationWorkflow extends WorkflowEntrypoint<

‎apps/web/app/problem/[problemId]/components/problem-render.tsx‎

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export default function ProblemRender({
9393
>([{ id: `test-case-${Date.now()}`, inputText: "" }]);
9494
const hasInitializedCustomTestCases = useRef(false);
9595
const [isAdjustingDifficulty, setIsAdjustingDifficulty] = useState(false);
96+
const [isRegeneratingSimilar, setIsRegeneratingSimilar] = useState(false);
9697

9798
const {
9899
isLoading: isProblemTextLoading,
@@ -506,10 +507,21 @@ export default function ProblemRender({
506507
</div>
507508
)}
508509
{generationError && overallStatus.type === "error" && (
509-
<div className="text-xs text-destructive">
510-
{typeof generationError === "string"
511-
? generationError
512-
: String(generationError)}
510+
<div className="flex items-center justify-between gap-2">
511+
<div className="text-xs text-destructive">
512+
{typeof generationError === "string"
513+
? generationError
514+
: String(generationError)}
515+
</div>
516+
<Button
517+
variant="outline"
518+
size="sm"
519+
onClick={handleRegenerateSimilar}
520+
disabled={isRegeneratingSimilar || !selectedModel}
521+
className="h-6 text-xs"
522+
>
523+
{isRegeneratingSimilar ? "Creating..." : "Try Again"}
524+
</Button>
513525
</div>
514526
)}
515527
</div>
@@ -714,6 +726,25 @@ export default function ProblemRender({
714726
}
715727
};
716728

729+
const handleRegenerateSimilar = async () => {
730+
if (!selectedModel) return;
731+
setIsRegeneratingSimilar(true);
732+
try {
733+
const result = await createProblem(
734+
selectedModel,
735+
user.apiKey,
736+
true,
737+
undefined,
738+
{ problemId, direction: "similar" },
739+
);
740+
router.push(`/problem/${result.problemId}`);
741+
} catch (error) {
742+
console.error("Failed to regenerate similar problem:", error);
743+
} finally {
744+
setIsRegeneratingSimilar(false);
745+
}
746+
};
747+
717748
return (
718749
<div className="h-screen w-screen flex flex-col overflow-hidden">
719750
<div className="w-full p-4 flex items-center justify-between gap-4 border-b flex-shrink-0">
@@ -749,18 +780,36 @@ export default function ProblemRender({
749780
variant="outline"
750781
size="sm"
751782
onClick={() => handleAdjustDifficulty("easier")}
752-
disabled={isAdjustingDifficulty || !selectedModel}
783+
disabled={
784+
isAdjustingDifficulty || isRegeneratingSimilar || !selectedModel
785+
}
753786
>
754787
{isAdjustingDifficulty ? "Creating..." : "Make Easier"}
755788
</Button>
756789
<Button
757790
variant="outline"
758791
size="sm"
759792
onClick={() => handleAdjustDifficulty("harder")}
760-
disabled={isAdjustingDifficulty || !selectedModel}
793+
disabled={
794+
isAdjustingDifficulty || isRegeneratingSimilar || !selectedModel
795+
}
761796
>
762797
{isAdjustingDifficulty ? "Creating..." : "Make Harder"}
763798
</Button>
799+
{(isWorkflowErrored || isFailed) && (
800+
<Button
801+
variant="outline"
802+
size="sm"
803+
onClick={handleRegenerateSimilar}
804+
disabled={
805+
isAdjustingDifficulty ||
806+
isRegeneratingSimilar ||
807+
!selectedModel
808+
}
809+
>
810+
{isRegeneratingSimilar ? "Creating..." : "Regenerate Similar"}
811+
</Button>
812+
)}
764813
</div>
765814
</div>
766815
<div className="flex items-center gap-4">
@@ -832,13 +881,6 @@ export default function ProblemRender({
832881
</div>
833882
)}
834883

835-
{generationError && (
836-
<Alert variant="destructive">
837-
<AlertTitle>Generation Error</AlertTitle>
838-
<AlertDescription>{generationError}</AlertDescription>
839-
</Alert>
840-
)}
841-
842884
<div className="space-y-4">
843885
<StepSection
844886
step="generateProblemText"

‎packages/api-types/src/schemas/problems.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ export const StartFromSchema = z
2323
problemId: z.string().uuid().openapi({
2424
description: "ID of existing problem to base this new problem on",
2525
}),
26-
direction: z.enum(["easier", "harder"]).openapi({
27-
description: "Direction to adjust difficulty",
26+
direction: z.enum(["easier", "harder", "similar"]).openapi({
27+
description: "Direction to adjust difficulty or regenerate similar",
2828
}),
2929
})
3030
.openapi("StartFrom");

0 commit comments

Comments
 (0)