Skip to content

Commit e1fc171

Browse files
committed
fix: split oversized scout-for-lol files to pass max-lines lint
Extract alert components and utility functions from results-panel.tsx (603 -> 390 lines) and rule operations from use-sound-pack-editor.tsx (509 -> 410 lines) to get both under the 500-line limit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7b95bc8 commit e1fc171

5 files changed

Lines changed: 406 additions & 334 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* Alert and info box components for the results panel
3+
*/
4+
5+
export function ValidationErrorAlert({
6+
error,
7+
onDismiss,
8+
}: {
9+
error: string;
10+
onDismiss: () => void;
11+
}) {
12+
return (
13+
<div className="mb-4 p-4 rounded-xl bg-defeat-50 border border-defeat-200 animate-fade-in">
14+
<div className="flex items-start gap-3">
15+
<svg
16+
className="w-5 h-5 text-defeat-500 shrink-0 mt-0.5"
17+
fill="currentColor"
18+
viewBox="0 0 20 20"
19+
>
20+
<path
21+
fillRule="evenodd"
22+
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
23+
clipRule="evenodd"
24+
/>
25+
</svg>
26+
<div className="flex-1">
27+
<div className="font-semibold text-defeat-900 mb-1">
28+
Cannot Generate Review
29+
</div>
30+
<div className="text-sm text-defeat-700">{error}</div>
31+
</div>
32+
<button
33+
onClick={onDismiss}
34+
className="text-defeat-400 hover:text-defeat-600"
35+
>
36+
<svg
37+
className="w-4 h-4"
38+
fill="none"
39+
stroke="currentColor"
40+
viewBox="0 0 24 24"
41+
>
42+
<path
43+
strokeLinecap="round"
44+
strokeLinejoin="round"
45+
strokeWidth={2}
46+
d="M6 18L18 6M6 6l12 12"
47+
/>
48+
</svg>
49+
</button>
50+
</div>
51+
</div>
52+
);
53+
}
54+
55+
export function GenerationErrorAlert({ error }: { error: string }) {
56+
return (
57+
<div className="mb-4 p-4 rounded-xl bg-defeat-50 border border-defeat-200 animate-fade-in">
58+
<div className="flex items-start gap-3">
59+
<svg
60+
className="w-5 h-5 text-defeat-500 shrink-0 mt-0.5"
61+
fill="currentColor"
62+
viewBox="0 0 20 20"
63+
>
64+
<path
65+
fillRule="evenodd"
66+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
67+
clipRule="evenodd"
68+
/>
69+
</svg>
70+
<div className="flex-1">
71+
<div className="font-semibold text-defeat-900 mb-1">
72+
Generation Failed
73+
</div>
74+
<div className="text-sm text-defeat-700">{error}</div>
75+
</div>
76+
</div>
77+
</div>
78+
);
79+
}
80+
81+
export function NoMatchInfoBox() {
82+
return (
83+
<div className="mb-4 p-4 rounded-xl bg-victory-50 border border-victory-200 text-sm text-victory-800 flex items-center gap-3">
84+
<svg
85+
className="w-5 h-5 text-victory-500 shrink-0"
86+
fill="none"
87+
stroke="currentColor"
88+
viewBox="0 0 24 24"
89+
>
90+
<path
91+
strokeLinecap="round"
92+
strokeLinejoin="round"
93+
strokeWidth={2}
94+
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
95+
/>
96+
</svg>
97+
<span>
98+
No match selected. Select a match from the browser to generate a review.
99+
</span>
100+
</div>
101+
);
102+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* Utility functions for the results panel
3+
*/
4+
import type {
5+
ReviewConfig,
6+
GenerationResult,
7+
} from "@scout-for-lol/frontend/lib/review-tool/config/schema";
8+
import type {
9+
CompletedMatch,
10+
ArenaMatch,
11+
RawMatch,
12+
RawTimeline,
13+
} from "@scout-for-lol/data";
14+
import type { CostTracker } from "@scout-for-lol/frontend/lib/review-tool/costs";
15+
import { calculateCost } from "@scout-for-lol/frontend/lib/review-tool/costs";
16+
import {
17+
generateMatchReview,
18+
type GenerationProgress as GenerationProgressType,
19+
} from "@scout-for-lol/frontend/lib/review-tool/generator";
20+
import {
21+
saveCompletedEntry,
22+
type HistoryEntry,
23+
} from "@scout-for-lol/frontend/lib/review-tool/history-manager";
24+
import { ErrorSchema } from "./results-panel-timer.ts";
25+
26+
export function handleCancelPending(id: string) {
27+
// Pending entries are not persisted, so nothing to cancel
28+
console.log("[History] Cancel requested for pending entry:", id);
29+
}
30+
31+
/**
32+
* Build config snapshot from generation result metadata
33+
*/
34+
export function buildConfigSnapshot(
35+
metadata: GenerationResult["metadata"],
36+
): HistoryEntry["configSnapshot"] {
37+
const snapshot: HistoryEntry["configSnapshot"] = {};
38+
if (
39+
metadata.selectedPersonality !== undefined &&
40+
metadata.selectedPersonality.length > 0
41+
) {
42+
snapshot.personality = metadata.selectedPersonality;
43+
}
44+
if (
45+
metadata.imageDescription !== undefined &&
46+
metadata.imageDescription.length > 0
47+
) {
48+
snapshot.imageDescription = metadata.imageDescription;
49+
}
50+
return snapshot;
51+
}
52+
53+
/**
54+
* Track cost from a successful generation
55+
*/
56+
export function trackGenerationCost(
57+
result: GenerationResult,
58+
config: ReviewConfig,
59+
costTracker: CostTracker,
60+
): void {
61+
if (result.error !== undefined) {
62+
return;
63+
}
64+
const cost = calculateCost(
65+
result.metadata,
66+
config.textGeneration.model,
67+
config.imageGeneration.model,
68+
);
69+
void (async () => {
70+
try {
71+
await costTracker.add(cost);
72+
} catch {
73+
// Error handling is done in the cost tracker
74+
}
75+
})();
76+
}
77+
78+
/**
79+
* Parse an error into a user-friendly message string
80+
*/
81+
export function parseErrorMessage(error: unknown): string {
82+
return ErrorSchema.safeParse(error).success
83+
? ErrorSchema.parse(error).message
84+
: String(error);
85+
}
86+
87+
/**
88+
* Execute the review generation workflow
89+
*/
90+
export async function executeGeneration({
91+
match,
92+
rawMatch,
93+
rawTimeline,
94+
config,
95+
historyId,
96+
selectedHistoryId,
97+
costTracker,
98+
onResultGenerated,
99+
onProgressUpdate,
100+
}: {
101+
match: CompletedMatch | ArenaMatch;
102+
rawMatch: RawMatch;
103+
rawTimeline: RawTimeline;
104+
config: ReviewConfig;
105+
historyId: string;
106+
selectedHistoryId: string | undefined;
107+
costTracker: CostTracker;
108+
onResultGenerated: (result: GenerationResult) => void;
109+
onProgressUpdate: (historyId: string, p: GenerationProgressType) => void;
110+
}): Promise<void> {
111+
try {
112+
const generatedResult = await generateMatchReview({
113+
match,
114+
rawMatch,
115+
rawTimeline,
116+
config,
117+
onProgress: (p) => {
118+
onProgressUpdate(historyId, p);
119+
},
120+
});
121+
122+
if (selectedHistoryId === historyId) {
123+
onResultGenerated(generatedResult);
124+
}
125+
126+
const configSnapshot = buildConfigSnapshot(generatedResult.metadata);
127+
await saveCompletedEntry(historyId, generatedResult, configSnapshot);
128+
trackGenerationCost(generatedResult, config, costTracker);
129+
} catch (error) {
130+
const errorResult = {
131+
text: "",
132+
metadata: { textDurationMs: 0, imageGenerated: false },
133+
error: parseErrorMessage(error),
134+
};
135+
136+
if (selectedHistoryId === historyId) {
137+
onResultGenerated(errorResult);
138+
}
139+
140+
await saveCompletedEntry(historyId, errorResult, {});
141+
}
142+
}

0 commit comments

Comments
 (0)