dwijverma2 commited on
Commit
dfd3781
·
verified ·
1 Parent(s): 80c1248

Add app: page.js (main page)

Browse files
Files changed (1) hide show
  1. app/page.js +277 -0
app/page.js ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useState, useCallback } from "react";
4
+ import UploadArea from "@/components/UploadArea";
5
+ import SplitView from "@/components/SplitView";
6
+ import ControlBar from "@/components/ControlBar";
7
+ import Toast from "@/components/Toast";
8
+
9
+ export default function Home() {
10
+ const [sessionId, setSessionId] = useState(null);
11
+ const [fileName, setFileName] = useState("");
12
+ const [paragraphs, setParagraphs] = useState([]);
13
+ const [classifications, setClassifications] = useState([]);
14
+ const [headingFontSize, setHeadingFontSize] = useState(18);
15
+ const [loading, setLoading] = useState(false);
16
+ const [loadingMessage, setLoadingMessage] = useState("");
17
+ const [toast, setToast] = useState(null);
18
+ const [enriched, setEnriched] = useState(false);
19
+
20
+ const showToast = useCallback((message, type = "info") => {
21
+ setToast({ message, type });
22
+ setTimeout(() => setToast(null), 4000);
23
+ }, []);
24
+
25
+ // Step 1: Upload and parse the document
26
+ const handleUpload = useCallback(
27
+ async (file) => {
28
+ setLoading(true);
29
+ setLoadingMessage("Parsing document...");
30
+ setEnriched(false);
31
+ setClassifications([]);
32
+
33
+ try {
34
+ const formData = new FormData();
35
+ formData.append("file", file);
36
+
37
+ const res = await fetch("/api/parse-docx", {
38
+ method: "POST",
39
+ body: formData,
40
+ });
41
+
42
+ const data = await res.json();
43
+ if (!res.ok) throw new Error(data.error);
44
+
45
+ setSessionId(data.sessionId);
46
+ setFileName(data.fileName);
47
+ setParagraphs(data.paragraphs);
48
+ showToast(
49
+ `Parsed ${data.totalParagraphs} paragraphs from "${data.fileName}"`,
50
+ "success"
51
+ );
52
+ } catch (err) {
53
+ showToast(err.message, "error");
54
+ } finally {
55
+ setLoading(false);
56
+ setLoadingMessage("");
57
+ }
58
+ },
59
+ [showToast]
60
+ );
61
+
62
+ // Step 2: Run LLM enrichment
63
+ const handleEnrich = useCallback(
64
+ async (model, customPrompt) => {
65
+ if (!sessionId) return;
66
+
67
+ setLoading(true);
68
+ setLoadingMessage(
69
+ "Analyzing document with AI... This may take a minute."
70
+ );
71
+
72
+ try {
73
+ const res = await fetch("/api/enrich", {
74
+ method: "POST",
75
+ headers: { "Content-Type": "application/json" },
76
+ body: JSON.stringify({
77
+ sessionId,
78
+ model: model || "llama3",
79
+ customPrompt: customPrompt || undefined,
80
+ headingFontSize,
81
+ }),
82
+ });
83
+
84
+ const data = await res.json();
85
+ if (!res.ok) throw new Error(data.error);
86
+
87
+ setClassifications(data.classifications);
88
+ setEnriched(true);
89
+ showToast(
90
+ `Found ${data.headingsFound} headings with ${headingFontSize}pt font size`,
91
+ "success"
92
+ );
93
+ } catch (err) {
94
+ showToast(err.message, "error");
95
+ } finally {
96
+ setLoading(false);
97
+ setLoadingMessage("");
98
+ }
99
+ },
100
+ [sessionId, headingFontSize, showToast]
101
+ );
102
+
103
+ // Step 3: Apply manual changes
104
+ const handleApplyChanges = useCallback(
105
+ async (headingIndices) => {
106
+ if (!sessionId) return;
107
+
108
+ setLoading(true);
109
+ setLoadingMessage("Applying your changes...");
110
+
111
+ try {
112
+ const res = await fetch("/api/apply-changes", {
113
+ method: "POST",
114
+ headers: { "Content-Type": "application/json" },
115
+ body: JSON.stringify({
116
+ sessionId,
117
+ headingIndices,
118
+ headingFontSize,
119
+ }),
120
+ });
121
+
122
+ const data = await res.json();
123
+ if (!res.ok) throw new Error(data.error);
124
+
125
+ setClassifications(data.classifications);
126
+ setEnriched(true);
127
+ showToast(
128
+ `Applied ${data.headingsApplied} heading(s) at ${headingFontSize}pt`,
129
+ "success"
130
+ );
131
+ } catch (err) {
132
+ showToast(err.message, "error");
133
+ } finally {
134
+ setLoading(false);
135
+ setLoadingMessage("");
136
+ }
137
+ },
138
+ [sessionId, headingFontSize, showToast]
139
+ );
140
+
141
+ // Step 4: Download
142
+ const handleDownload = useCallback(async () => {
143
+ if (!sessionId || !enriched) return;
144
+
145
+ try {
146
+ const res = await fetch(`/api/download?sessionId=${sessionId}`);
147
+ if (!res.ok) {
148
+ const data = await res.json();
149
+ throw new Error(data.error);
150
+ }
151
+
152
+ const blob = await res.blob();
153
+ const url = URL.createObjectURL(blob);
154
+ const a = document.createElement("a");
155
+ a.href = url;
156
+ a.download = fileName
157
+ ? fileName.replace(".docx", "_enriched.docx")
158
+ : "enriched_sop.docx";
159
+ document.body.appendChild(a);
160
+ a.click();
161
+ document.body.removeChild(a);
162
+ URL.revokeObjectURL(url);
163
+ showToast("Download started!", "success");
164
+ } catch (err) {
165
+ showToast(err.message, "error");
166
+ }
167
+ }, [sessionId, enriched, fileName, showToast]);
168
+
169
+ // Reset everything
170
+ const handleReset = useCallback(() => {
171
+ setSessionId(null);
172
+ setFileName("");
173
+ setParagraphs([]);
174
+ setClassifications([]);
175
+ setEnriched(false);
176
+ }, []);
177
+
178
+ return (
179
+ <div className="min-h-screen flex flex-col" style={{ background: "var(--bg-secondary)" }}>
180
+ {toast && <Toast message={toast.message} type={toast.type} />}
181
+
182
+ {/* Top navbar */}
183
+ <header
184
+ className="flex items-center justify-between px-5 py-3 border-b"
185
+ style={{
186
+ background: "var(--bg-primary)",
187
+ borderColor: "var(--border-color)",
188
+ }}
189
+ >
190
+ <div className="flex items-center gap-3">
191
+ <div
192
+ className="w-8 h-8 rounded-lg flex items-center justify-center text-sm font-bold"
193
+ style={{ background: "var(--accent)", color: "var(--bg-secondary)" }}
194
+ >
195
+ S
196
+ </div>
197
+ <h1 className="text-base font-semibold" style={{ color: "var(--text-primary)" }}>
198
+ SOP Heading Enricher
199
+ </h1>
200
+ </div>
201
+
202
+ <div className="flex items-center gap-3">
203
+ {fileName && (
204
+ <span
205
+ className="text-xs px-3 py-1 rounded-full"
206
+ style={{
207
+ background: "var(--bg-tertiary)",
208
+ color: "var(--text-secondary)",
209
+ }}
210
+ >
211
+ {fileName}
212
+ </span>
213
+ )}
214
+ {sessionId && (
215
+ <button
216
+ onClick={handleReset}
217
+ className="text-xs px-3 py-1.5 rounded-lg transition-colors cursor-pointer"
218
+ style={{
219
+ background: "var(--bg-tertiary)",
220
+ color: "var(--text-secondary)",
221
+ }}
222
+ onMouseEnter={(e) =>
223
+ (e.target.style.background = "var(--bg-hover)")
224
+ }
225
+ onMouseLeave={(e) =>
226
+ (e.target.style.background = "var(--bg-tertiary)")
227
+ }
228
+ >
229
+ New Document
230
+ </button>
231
+ )}
232
+ </div>
233
+ </header>
234
+
235
+ {/* Main content */}
236
+ <main className="flex-1 flex flex-col">
237
+ {!sessionId ? (
238
+ <UploadArea onUpload={handleUpload} loading={loading} />
239
+ ) : (
240
+ <>
241
+ <ControlBar
242
+ onEnrich={handleEnrich}
243
+ onApplyChanges={handleApplyChanges}
244
+ onDownload={handleDownload}
245
+ enriched={enriched}
246
+ loading={loading}
247
+ loadingMessage={loadingMessage}
248
+ headingFontSize={headingFontSize}
249
+ onFontSizeChange={setHeadingFontSize}
250
+ classifications={classifications}
251
+ />
252
+ <SplitView
253
+ paragraphs={paragraphs}
254
+ classifications={classifications}
255
+ enriched={enriched}
256
+ />
257
+ </>
258
+ )}
259
+ </main>
260
+
261
+ {/* Loading overlay */}
262
+ {loading && (
263
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
264
+ <div
265
+ className="rounded-xl px-8 py-6 flex flex-col items-center gap-3 shadow-2xl"
266
+ style={{ background: "var(--bg-primary)" }}
267
+ >
268
+ <div className="spinner" />
269
+ <p className="text-sm" style={{ color: "var(--text-secondary)" }}>
270
+ {loadingMessage || "Processing..."}
271
+ </p>
272
+ </div>
273
+ </div>
274
+ )}
275
+ </div>
276
+ );
277
+ }