Skip to content

Commit 0c313be

Browse files
authored
Merge pull request #397 from ACM-VIT/dev
Dev
2 parents 2e6c079 + 1a42a6f commit 0c313be

9 files changed

Lines changed: 93 additions & 24 deletions

File tree

app/(app)/home/exams-marquee.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import React from "react";
22
import Link from "next/link";
33
import type { UpcomingExamItem } from "@/lib/data/upcoming-exams";
44

5+
const marqueeShellClassName =
6+
"relative left-1/2 min-h-[5.875rem] w-screen -translate-x-1/2 border-y border-black/10 py-4 md:min-h-[7rem] md:py-5 dark:border-[#D5D5D5]/10 md:border-white/15 dark:md:border-white/15";
7+
58
function MarqueeItem({
69
item,
710
prefetch,
@@ -72,7 +75,7 @@ export default async function ExamsMarquee({ items }: { items: UpcomingExamItem[
7275
<div
7376
role="region"
7477
aria-label="Upcoming exams"
75-
className="relative left-1/2 w-screen -translate-x-1/2 border-y border-black/10 py-4 md:py-5 dark:border-[#D5D5D5]/10 md:border-white/15 dark:md:border-white/15"
78+
className={marqueeShellClassName}
7679
>
7780
<div
7881
aria-hidden="true"
@@ -89,3 +92,14 @@ export default async function ExamsMarquee({ items }: { items: UpcomingExamItem[
8992
</div>
9093
);
9194
}
95+
96+
export function ExamsMarqueeFallback() {
97+
return (
98+
<div aria-hidden="true" className={marqueeShellClassName}>
99+
<div className="flex flex-col gap-3 md:gap-4">
100+
<div className="h-6 md:h-7" />
101+
<div className="h-6 md:h-7" />
102+
</div>
103+
</div>
104+
);
105+
}

app/(app)/home/home.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { auth } from "@/app/auth";
33
import { GradientText } from "@/app/components/landing_page/landing";
44
import ExamCookerLogo from "@/app/components/common/exam-cooker-logo";
55
import DirectionalTransition from "@/app/components/common/directional-transition";
6-
import ExamsMarquee from "./exams-marquee";
6+
import ExamsMarquee, { ExamsMarqueeFallback } from "./exams-marquee";
77
import { getSearchableCourses } from "@/lib/data/course-catalog";
88
import { getUpcomingExams } from "@/lib/data/upcoming-exams";
99
import CourseSearch from "./course-search";
@@ -82,7 +82,7 @@ const Home = () => {
8282
</div>
8383

8484
<div className="ec-home-marquee-offset pb-4 md:pb-6 lg:pb-8">
85-
<Suspense fallback={null}>
85+
<Suspense fallback={<ExamsMarqueeFallback />}>
8686
<HomeMarqueeSection />
8787
</Suspense>
8888
</div>

app/api/pdf/markdown/route.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
PdfPaperDocumentSchema,
77
PdfPaperQuestionSchema,
88
buildPdfPaperMarkdown,
9+
getPdfMarkdownLanguageModel,
910
getPdfMarkdownModel,
1011
} from "@/lib/ai/pdf-markdown";
1112
import type { PdfPaperQuestion } from "@/lib/ai/pdf-markdown";
@@ -183,6 +184,19 @@ function getSafePdfFileName(fileName: string) {
183184
return /\.pdf$/i.test(trimmed) ? trimmed : `${trimmed}.pdf`;
184185
}
185186

187+
function getErrorMessage(error: unknown) {
188+
return error instanceof Error ? error.message : String(error || "");
189+
}
190+
191+
function getStreamErrorMessage(error: unknown, streamError: unknown) {
192+
const fallbackMessage = getErrorMessage(error);
193+
if (streamError) {
194+
return getErrorMessage(streamError) || fallbackMessage;
195+
}
196+
197+
return fallbackMessage || "Failed to convert this PDF to Markdown.";
198+
}
199+
186200
async function fetchPdfBuffer(fileUrl: URL) {
187201
const response = await fetch(fileUrl, {
188202
cache: "no-store",
@@ -287,9 +301,11 @@ export async function POST(request: NextRequest) {
287301
);
288302
}
289303

290-
const model = getPdfMarkdownModel();
304+
const model = getPdfMarkdownLanguageModel();
305+
const modelId = getPdfMarkdownModel();
291306

292307
try {
308+
let streamError: unknown = null;
293309
const result = streamText({
294310
model,
295311
system: PDF_MARKDOWN_SYSTEM_PROMPT,
@@ -319,11 +335,19 @@ export async function POST(request: NextRequest) {
319335
"A faithful ordered list of only question numbers, question text, and marks.",
320336
}),
321337
abortSignal: request.signal,
322-
temperature: 0,
323338
maxOutputTokens: 12000,
324339
experimental_include: {
325340
requestBody: false,
326341
},
342+
onError: ({ error }) => {
343+
streamError = error;
344+
console.error("[pdf-markdown] stream error", error);
345+
},
346+
providerOptions: {
347+
openai: {
348+
store: false,
349+
},
350+
},
327351
});
328352

329353
const encoder = new TextEncoder();
@@ -357,15 +381,12 @@ export async function POST(request: NextRequest) {
357381
type: "done",
358382
paper,
359383
markdown: buildPdfPaperMarkdown(paper),
360-
model,
384+
model: modelId,
361385
});
362386
} catch (error) {
363387
enqueue({
364388
type: "error",
365-
error:
366-
error instanceof Error
367-
? error.message
368-
: "Failed to convert this PDF to Markdown.",
389+
error: getStreamErrorMessage(error, streamError),
369390
});
370391
} finally {
371392
controller.close();
@@ -381,7 +402,7 @@ export async function POST(request: NextRequest) {
381402
"Cache-Control": "no-store",
382403
"Content-Type": "application/x-ndjson; charset=utf-8",
383404
"X-Accel-Buffering": "no",
384-
"X-ExamCooker-AI-Model": model,
405+
"X-ExamCooker-AI-Model": modelId,
385406
},
386407
});
387408
} catch (error) {

app/components/capacitor-bridge.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ export default function CapacitorBridge() {
123123
platform === "ios" ? "data-native-ios-tabs" : "data-native-android-tabs",
124124
"true",
125125
);
126+
document.documentElement.removeAttribute("data-native-tabs-pending");
127+
document.documentElement.removeAttribute("data-native-ios-tabs-pending");
128+
document.documentElement.removeAttribute("data-native-android-tabs-pending");
126129
const tabSelectionListener = await NativeTabs.addListener("tabSelected", (info) => {
127130
const route = info.tab.route ?? "/";
128131
const nextPath = route.startsWith("/") ? route : `/${route}`;
@@ -137,6 +140,9 @@ export default function CapacitorBridge() {
137140
document.documentElement.removeAttribute("data-native-tabs");
138141
document.documentElement.removeAttribute("data-native-ios-tabs");
139142
document.documentElement.removeAttribute("data-native-android-tabs");
143+
document.documentElement.removeAttribute("data-native-tabs-pending");
144+
document.documentElement.removeAttribute("data-native-ios-tabs-pending");
145+
document.documentElement.removeAttribute("data-native-android-tabs-pending");
140146
window.dispatchEvent(new Event("examcooker:use-web-tab-bar"));
141147
}
142148
}
@@ -225,6 +231,9 @@ export default function CapacitorBridge() {
225231
root.removeAttribute("data-native-ios");
226232
root.removeAttribute("data-native-ios-tabs");
227233
root.removeAttribute("data-native-android-tabs");
234+
root.removeAttribute("data-native-tabs-pending");
235+
root.removeAttribute("data-native-ios-tabs-pending");
236+
root.removeAttribute("data-native-android-tabs-pending");
228237
};
229238
}, [router]);
230239

app/components/mobile-tab-bar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export default function MobileTabBar({ toolsSheetOpen = false }: Props) {
146146
<nav
147147
aria-label="Primary"
148148
style={{ viewTransitionName: "persistent-mobile-tab-bar" }}
149-
className={`fixed inset-x-0 bottom-0 z-[48] pb-[max(env(safe-area-inset-bottom),0px)] backdrop-blur-md transition-transform duration-200 lg:hidden ${
149+
className={`ec-mobile-tab-bar fixed inset-x-0 bottom-0 z-[48] pb-[max(env(safe-area-inset-bottom),0px)] backdrop-blur-md transition-transform duration-200 lg:hidden ${
150150
nativeAndroid
151151
? `border-t border-black/8 bg-white/96 shadow-[0_-10px_24px_rgba(15,23,42,0.10)] supports-[backdrop-filter]:bg-white/90 dark:border-[#D5D5D5]/10 dark:bg-[#09101E]/96 dark:shadow-[0_-16px_32px_rgba(0,0,0,0.45)] dark:supports-[backdrop-filter]:bg-[#09101E]/90 ${
152152
keyboardOpen ? "translate-y-[calc(100%+env(safe-area-inset-bottom)+12px)]" : "translate-y-0"

app/components/pdf-viewer-client.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
"use client";
22

3-
import { preconnect, preload } from "react-dom";
3+
import { preconnect } from "react-dom";
44

5-
import {
6-
PDFIUM_WASM_URL,
7-
preloadPdfiumEngine,
8-
} from "@/lib/pdf/pdfium-engine-cache";
5+
import { preloadPdfiumEngine } from "@/lib/pdf/pdfium-engine-cache";
96
import PDFViewer from "./pdfviewer";
107

118
if (typeof window !== "undefined") {
@@ -30,12 +27,9 @@ export default function PDFViewerClient({
3027
fileUrl: string;
3128
fileName?: string;
3229
}) {
33-
preload(PDFIUM_WASM_URL, { as: "fetch" });
34-
3530
const remoteOrigin = getRemoteOrigin(fileUrl);
3631
if (remoteOrigin) {
3732
preconnect(remoteOrigin, { crossOrigin: "anonymous" });
38-
preload(fileUrl, { as: "fetch", crossOrigin: "anonymous" });
3933
}
4034

4135
return <PDFViewer key={fileUrl} fileUrl={fileUrl} fileName={fileName} />;

app/globals.css

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@
197197
animation-play-state: paused;
198198
}
199199

200+
html[data-native-tabs="true"] .ec-mobile-tab-bar,
201+
html[data-native-tabs-pending="true"] .ec-mobile-tab-bar {
202+
display: none;
203+
}
204+
200205
@media (prefers-reduced-motion: reduce) {
201206
.animate-marquee,
202207
.animate-marquee-reverse {
@@ -458,17 +463,20 @@
458463
}
459464

460465
@media (max-width: 1023px) {
461-
html:not([data-native-tabs="true"]) .ec-home-marquee-offset {
466+
html:not([data-native-tabs="true"]):not([data-native-tabs-pending="true"]) .ec-home-marquee-offset {
462467
position: relative;
463468
top: calc(-4.75rem - env(safe-area-inset-bottom));
464469
}
465470

466471
html[data-native-ios-tabs="true"] .ec-app-main,
467-
html[data-native-android-tabs="true"] .ec-app-main {
472+
html[data-native-android-tabs="true"] .ec-app-main,
473+
html[data-native-ios-tabs-pending="true"] .ec-app-main,
474+
html[data-native-android-tabs-pending="true"] .ec-app-main {
468475
padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
469476
}
470477

471-
html[data-native-ios-tabs="true"] {
478+
html[data-native-ios-tabs="true"],
479+
html[data-native-ios-tabs-pending="true"] {
472480
--ec-voice-agent-bottom-offset: 3.0625rem;
473481
}
474482
}

app/layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ export default function RootLayout({
9898
<Script id="theme-init" strategy="beforeInteractive">
9999
{"(function(){var r=document.documentElement;function m(q){return window.matchMedia&&window.matchMedia(q).matches;}function a(d){var bg=d?'#0C1222':'#C2E6EC';r.classList.toggle('dark',d);r.dataset.theme=d?'dark':'light';r.style.colorScheme=d?'dark':'light';r.style.setProperty('--ec-app-bg',bg);r.style.backgroundColor=bg;}try{var t=localStorage.getItem('theme');var mobile=m('(max-width: 767px), (pointer: coarse)');var d=t==='dark'||(t!=='light'&&(mobile?m('(prefers-color-scheme: dark)'):true));a(d);}catch(e){a(true);}})();"}
100100
</Script>
101+
<Script id="native-shell-init" strategy="beforeInteractive">
102+
{"(function(){try{var c=window.Capacitor;if(!c||typeof c.isNativePlatform!=='function'||!c.isNativePlatform())return;var p=typeof c.getPlatform==='function'?c.getPlatform():'';if(p!=='ios'&&p!=='android')return;var r=document.documentElement;r.dataset.nativePlatform=p;r.toggleAttribute('data-native-ios',p==='ios');r.toggleAttribute('data-native-android',p==='android');r.setAttribute('data-native-tabs-pending','true');r.setAttribute(p==='ios'?'data-native-ios-tabs-pending':'data-native-android-tabs-pending','true');}catch(e){}})();"}
103+
</Script>
101104
</head>
102105
<body
103106
className={`${plus_jakarta_sans.className} antialiased bg-[#C2E6EC] dark:bg-[#0C1222]`}

lib/ai/pdf-markdown.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import { openai } from "@ai-sdk/openai";
12
import { z } from "zod";
23

3-
const DEFAULT_PDF_MARKDOWN_MODEL = "openai/gpt-5.4-nano";
4+
const DEFAULT_PDF_MARKDOWN_MODEL = "gpt-5.4-nano";
5+
6+
function normalizeOpenAiModelId(modelId: string) {
7+
return modelId.replace(/^openai\//, "");
8+
}
49

510
const NullableTextSchema = z.string().trim().nullable();
611

@@ -29,6 +34,21 @@ export function getPdfMarkdownModel() {
2934
return process.env.AI_PDF_MARKDOWN_MODEL?.trim() || DEFAULT_PDF_MARKDOWN_MODEL;
3035
}
3136

37+
export function getPdfMarkdownLanguageModel() {
38+
const modelId = getPdfMarkdownModel();
39+
40+
const isOpenAiModel = !modelId.includes("/") || modelId.startsWith("openai/");
41+
if (isOpenAiModel && process.env.OPENAI_API_KEY?.trim()) {
42+
return openai.responses(normalizeOpenAiModelId(modelId));
43+
}
44+
45+
if (process.env.AI_GATEWAY_API_KEY?.trim()) {
46+
return modelId.includes("/") ? modelId : `openai/${modelId}`;
47+
}
48+
49+
return openai.responses(normalizeOpenAiModelId(modelId));
50+
}
51+
3252
export function buildPdfPaperMarkdown(paper: PdfPaperDocument) {
3353
const lines: string[] = ["# Questions", ""];
3454

0 commit comments

Comments
 (0)