anky2002 commited on
Commit
78229a4
·
2 Parent(s): 59a9b4774929b6

Merge branch 'main' of https://huggingface.co/spaces/gaurv007/ClauseGuard

Browse files
web/app/api/analyze/route.ts CHANGED
@@ -1,12 +1,11 @@
1
  import { NextRequest, NextResponse } from "next/server";
2
- import { createClient } from "@/lib/supabase/server";
3
 
4
- const API_URL = process.env.CLAUSEGUARD_API_URL || "https://gaurv007-clauseguard-api.hf.space";
5
 
6
  export async function POST(req: NextRequest) {
7
  try {
8
  const body = await req.json();
9
- const { text, source_url } = body;
10
 
11
  if (!text || typeof text !== "string" || text.trim().length < 50) {
12
  return NextResponse.json(
@@ -15,34 +14,157 @@ export async function POST(req: NextRequest) {
15
  );
16
  }
17
 
18
- // Forward auth token to backend
19
- const headers: Record<string, string> = { "Content-Type": "application/json" };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  try {
21
- const supabase = await createClient();
22
- const { data: { session } } = await supabase.auth.getSession();
23
- if (session?.access_token) {
24
- headers["Authorization"] = `Bearer ${session.access_token}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
- } catch {}
 
 
 
 
 
 
 
27
 
28
- const response = await fetch(`${API_URL}/api/analyze`, {
29
- method: "POST",
30
- headers,
31
- body: JSON.stringify({ text, source_url }),
32
- });
 
 
 
 
 
 
 
 
 
33
 
34
- if (!response.ok) {
35
- const errText = await response.text().catch(() => "");
36
- console.error(`Backend error ${response.status}: ${errText}`);
37
- throw new Error(`Backend API error: ${response.status}`);
 
 
 
 
 
 
 
 
38
  }
39
 
40
- const results = await response.json();
41
- return NextResponse.json(results);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  } catch (error: any) {
43
  console.error("Analyze error:", error.message);
44
  return NextResponse.json(
45
- { error: "Analysis failed. The backend may be starting up — try again in 30 seconds." },
46
  { status: 500 }
47
  );
48
  }
 
1
  import { NextRequest, NextResponse } from "next/server";
 
2
 
3
+ const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space";
4
 
5
  export async function POST(req: NextRequest) {
6
  try {
7
  const body = await req.json();
8
+ const { text } = body;
9
 
10
  if (!text || typeof text !== "string" || text.trim().length < 50) {
11
  return NextResponse.json(
 
14
  );
15
  }
16
 
17
+ // Step 1: Submit to Gradio Space
18
+ const submitRes = await fetch(`${GRADIO_URL}/gradio_api/call/_analysis_and_index`, {
19
+ method: "POST",
20
+ headers: { "Content-Type": "application/json" },
21
+ body: JSON.stringify({ data: [text] }),
22
+ });
23
+
24
+ if (!submitRes.ok) {
25
+ throw new Error(`Gradio submit failed: ${submitRes.status}`);
26
+ }
27
+
28
+ const { event_id } = await submitRes.json();
29
+ if (!event_id) throw new Error("No event_id from Gradio");
30
+
31
+ // Step 2: Poll for result (SSE)
32
+ // The Gradio API streams but we need the full response
33
+ let resultText = "";
34
+ let attempts = 0;
35
+ const maxAttempts = 60; // 60 seconds max
36
+
37
+ while (attempts < maxAttempts) {
38
+ const resultRes = await fetch(
39
+ `${GRADIO_URL}/gradio_api/call/_analysis_and_index/${event_id}`,
40
+ { headers: { Accept: "text/event-stream" } }
41
+ );
42
+
43
+ resultText = await resultRes.text();
44
+
45
+ if (resultText.includes("event: complete")) break;
46
+ if (resultText.includes("event: error")) {
47
+ const errMatch = resultText.match(/data:\s*(.+)/);
48
+ throw new Error(errMatch ? errMatch[1] : "Analysis failed in backend");
49
+ }
50
+
51
+ // Wait 1 second and retry
52
+ await new Promise(r => setTimeout(r, 1000));
53
+ attempts++;
54
+ }
55
+
56
+ if (!resultText.includes("event: complete")) {
57
+ throw new Error("Analysis timed out");
58
+ }
59
+
60
+ // Step 3: Parse the SSE data
61
+ // Format: "event: complete\ndata: [...]"
62
+ // The data contains HTML with literal newlines, so we need to find 'data: ' after 'event: complete'
63
+ const completeIdx = resultText.indexOf("event: complete");
64
+ const dataIdx = resultText.indexOf("data: ", completeIdx);
65
+ if (dataIdx === -1) throw new Error("No data in response");
66
+
67
+ const dataStr = resultText.substring(dataIdx + 6).trim();
68
+
69
+ // Parse JSON — the HTML strings contain control characters so we need to handle that
70
+ // In JS, JSON.parse is more lenient with control chars in strings than Python's strict mode
71
+ let gradioData: any[];
72
  try {
73
+ gradioData = JSON.parse(dataStr);
74
+ } catch {
75
+ // If direct parse fails, try replacing problematic control characters
76
+ const cleaned = dataStr.replace(/[\x00-\x1f]/g, (ch: string) => {
77
+ if (ch === "\n") return "\\n";
78
+ if (ch === "\r") return "\\r";
79
+ if (ch === "\t") return "\\t";
80
+ return "";
81
+ });
82
+ gradioData = JSON.parse(cleaned);
83
+ }
84
+
85
+ // Step 4: Download the JSON report file (structured data)
86
+ // gradioData[8] is the JSON file object with { url, path, ... }
87
+ const jsonFileObj = gradioData[8];
88
+ if (!jsonFileObj?.url) {
89
+ throw new Error("No JSON report generated");
90
+ }
91
+
92
+ // Download immediately (temp files expire quickly)
93
+ const jsonRes = await fetch(jsonFileObj.url);
94
+ if (!jsonRes.ok) throw new Error("Failed to download analysis JSON");
95
+ const analysisData = await jsonRes.json();
96
+
97
+ // Step 5: Transform to frontend format
98
+ const riskScore = analysisData.risk?.score ?? 0;
99
+ const grade = analysisData.risk?.grade ?? "A";
100
+ const totalClauses = analysisData.metadata?.total_clauses ?? 0;
101
+ const flaggedCount = analysisData.metadata?.flagged_clauses ?? 0;
102
+
103
+ // Group clauses by text (multiple labels per clause)
104
+ const clauseMap = new Map<string, any>();
105
+ for (const cr of (analysisData.clauses || [])) {
106
+ if (!clauseMap.has(cr.text)) {
107
+ clauseMap.set(cr.text, { text: cr.text, categories: [] });
108
  }
109
+ clauseMap.get(cr.text)!.categories.push({
110
+ name: cr.label,
111
+ severity: cr.risk,
112
+ confidence: cr.confidence,
113
+ description: cr.description,
114
+ });
115
+ }
116
+ const results = Array.from(clauseMap.values());
117
 
118
+ // Parse redlines from HTML (gradioData[7])
119
+ const redlines: any[] = [];
120
+ const redlineHtml = typeof gradioData[7] === "string" ? gradioData[7] : "";
121
+ if (redlineHtml.includes("Clause Redlining")) {
122
+ // Split by redline card borders
123
+ const blocks = redlineHtml.split(/border-left:4px solid #/);
124
+ for (let i = 1; i < blocks.length; i++) {
125
+ const block = blocks[i];
126
+ const labelMatch = block.match(/font-weight:600[^>]*>([^<]+)<\/span>\s*<span[^>]*font-weight:600[^>]*>([^<]+)/);
127
+ const origMatch = block.match(/<del>([^<]*)<\/del>/);
128
+ const safeBlock = block.match(/Suggested Alternative[\s\S]*?<div[^>]*color:#166534[^>]*>([\s\S]*?)<\/div>/);
129
+ const legalMatch = block.match(/Legal Basis<\/div>\s*<div[^>]*>([^<]+)/);
130
+ const consumerMatch = block.match(/Consumer Standard<\/div>\s*<div[^>]*>([^<]+)/);
131
+ const isLLM = block.includes("LLM Refined");
132
 
133
+ if (labelMatch) {
134
+ redlines.push({
135
+ clause_label: labelMatch[1].trim(),
136
+ risk_level: labelMatch[2].trim(),
137
+ original_text: origMatch ? origMatch[1].trim() : "",
138
+ safe_alternative: safeBlock ? safeBlock[1].replace(/<[^>]+>/g, "").trim() : "",
139
+ legal_basis: legalMatch ? legalMatch[1].trim() : "",
140
+ consumer_standard: consumerMatch ? consumerMatch[1].trim() : "",
141
+ tier: isLLM ? "llm_refined" : "template",
142
+ });
143
+ }
144
+ }
145
  }
146
 
147
+ const modelStatus = analysisData.metadata?.model || "";
148
+
149
+ return NextResponse.json({
150
+ risk_score: riskScore,
151
+ grade,
152
+ total_clauses: totalClauses,
153
+ flagged_count: flaggedCount,
154
+ results,
155
+ entities: analysisData.entities || [],
156
+ contradictions: analysisData.contradictions || [],
157
+ obligations: analysisData.obligations || [],
158
+ compliance: analysisData.compliance || {},
159
+ redlines,
160
+ model: modelStatus.includes("loaded") ? "ml" : "regex",
161
+ latency_ms: 0,
162
+ session_id: null,
163
+ });
164
  } catch (error: any) {
165
  console.error("Analyze error:", error.message);
166
  return NextResponse.json(
167
+ { error: "Analysis failed: " + (error.message || "Try again in 30 seconds.") },
168
  { status: 500 }
169
  );
170
  }
web/app/api/chat/route.ts CHANGED
@@ -1,36 +1,68 @@
1
  import { NextRequest, NextResponse } from "next/server";
2
 
3
- const API_URL = process.env.CLAUSEGUARD_API_URL || "https://gaurv007-clauseguard-api.hf.space";
4
 
5
  export async function POST(req: NextRequest) {
6
  try {
7
  const body = await req.json();
8
- const { message, session_id, history } = body;
9
 
10
- if (!message || !session_id) {
11
  return NextResponse.json(
12
- { error: "message and session_id are required" },
13
  { status: 400 }
14
  );
15
  }
16
 
17
- const response = await fetch(`${API_URL}/api/chat`, {
 
 
 
18
  method: "POST",
19
  headers: { "Content-Type": "application/json" },
20
- body: JSON.stringify({ message, session_id, history: history || [] }),
21
  });
22
 
23
- if (!response.ok) {
24
- const err = await response.text().catch(() => "");
25
- throw new Error(err || `Backend error: ${response.status}`);
26
  }
27
 
28
- const result = await response.json();
29
- return NextResponse.json(result);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  } catch (error: any) {
31
  console.error("Chat error:", error.message);
32
  return NextResponse.json(
33
- { error: error.message || "Chat failed. Try again." },
34
  { status: 500 }
35
  );
36
  }
 
1
  import { NextRequest, NextResponse } from "next/server";
2
 
3
+ const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space";
4
 
5
  export async function POST(req: NextRequest) {
6
  try {
7
  const body = await req.json();
8
+ const { message, history } = body;
9
 
10
+ if (!message) {
11
  return NextResponse.json(
12
+ { error: "message is required" },
13
  { status: 400 }
14
  );
15
  }
16
 
17
+ // The Gradio ChatInterface endpoint is /chat
18
+ // It accepts: message (str), then the additional_inputs are handled by Gradio state
19
+ // We need to call the Gradio API with the message
20
+ const submitRes = await fetch(`${GRADIO_URL}/gradio_api/call/chat`, {
21
  method: "POST",
22
  headers: { "Content-Type": "application/json" },
23
+ body: JSON.stringify({ data: [message] }),
24
  });
25
 
26
+ if (!submitRes.ok) {
27
+ const errText = await submitRes.text().catch(() => "");
28
+ throw new Error(`Chat submit failed (${submitRes.status}): ${errText}`);
29
  }
30
 
31
+ const { event_id } = await submitRes.json();
32
+ if (!event_id) throw new Error("No event_id from Gradio chat");
33
+
34
+ // Poll for streaming result
35
+ const resultRes = await fetch(
36
+ `${GRADIO_URL}/gradio_api/call/chat/${event_id}`,
37
+ { headers: { Accept: "text/event-stream" } }
38
+ );
39
+
40
+ if (!resultRes.ok) {
41
+ throw new Error(`Chat result failed: ${resultRes.status}`);
42
+ }
43
+
44
+ const resultText = await resultRes.text();
45
+
46
+ // Find the complete event data
47
+ const dataMatch = resultText.match(/event:\s*complete\s*\ndata:\s*(.+)/);
48
+ if (!dataMatch) {
49
+ // Check for error
50
+ const errMatch = resultText.match(/event:\s*error\s*\ndata:\s*(.+)/);
51
+ if (errMatch) {
52
+ throw new Error(`Chat error: ${errMatch[1]}`);
53
+ }
54
+ throw new Error("No response from chatbot. Analyze a contract first in the Gradio Space, then try chatting.");
55
+ }
56
+
57
+ const responseData = JSON.parse(dataMatch[1]);
58
+ // The ChatInterface returns the response as a string
59
+ const responseText = typeof responseData === "string" ? responseData : responseData[0] || "";
60
+
61
+ return NextResponse.json({ response: responseText });
62
  } catch (error: any) {
63
  console.error("Chat error:", error.message);
64
  return NextResponse.json(
65
+ { error: error.message || "Chat failed. Make sure you analyzed a contract in the Gradio Space first." },
66
  { status: 500 }
67
  );
68
  }
web/app/api/compare/route.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { NextRequest, NextResponse } from "next/server";
2
 
3
- const API_URL = process.env.CLAUSEGUARD_API_URL || "https://gaurv007-clauseguard-api.hf.space";
4
 
5
  export async function POST(req: NextRequest) {
6
  try {
@@ -14,18 +14,49 @@ export async function POST(req: NextRequest) {
14
  );
15
  }
16
 
17
- const response = await fetch(`${API_URL}/api/compare`, {
 
18
  method: "POST",
19
  headers: { "Content-Type": "application/json" },
20
- body: JSON.stringify({ text_a, text_b }),
21
  });
22
 
23
- if (!response.ok) {
24
- throw new Error(`Backend error: ${response.status}`);
25
  }
26
 
27
- const result = await response.json();
28
- return NextResponse.json(result);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  } catch (error: any) {
30
  console.error("Compare error:", error.message);
31
  return NextResponse.json({ error: error.message || "Comparison failed" }, { status: 500 });
 
1
  import { NextRequest, NextResponse } from "next/server";
2
 
3
+ const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space";
4
 
5
  export async function POST(req: NextRequest) {
6
  try {
 
14
  );
15
  }
16
 
17
+ // Call Gradio Space API
18
+ const submitRes = await fetch(`${GRADIO_URL}/gradio_api/call/run_comparison`, {
19
  method: "POST",
20
  headers: { "Content-Type": "application/json" },
21
+ body: JSON.stringify({ data: [text_a, text_b] }),
22
  });
23
 
24
+ if (!submitRes.ok) {
25
+ throw new Error(`Gradio submit failed: ${submitRes.status}`);
26
  }
27
 
28
+ const { event_id } = await submitRes.json();
29
+ if (!event_id) throw new Error("No event_id from Gradio");
30
+
31
+ // Poll for result
32
+ const resultRes = await fetch(
33
+ `${GRADIO_URL}/gradio_api/call/run_comparison/${event_id}`,
34
+ { headers: { Accept: "text/event-stream" } }
35
+ );
36
+
37
+ if (!resultRes.ok) {
38
+ throw new Error(`Gradio result failed: ${resultRes.status}`);
39
+ }
40
+
41
+ const resultText = await resultRes.text();
42
+ const dataMatch = resultText.match(/event:\s*complete\s*\ndata:\s*(.+)/);
43
+ if (!dataMatch) throw new Error("No complete event from Gradio");
44
+
45
+ const gradioData = JSON.parse(dataMatch[1]);
46
+ // gradioData[0] = comparison HTML
47
+ // gradioData[1] = raw JSON comparison data
48
+
49
+ const comparisonResult = gradioData[1];
50
+ if (typeof comparisonResult === "object" && comparisonResult !== null) {
51
+ return NextResponse.json(comparisonResult);
52
+ }
53
+
54
+ // If it's a string (JSON stringified), parse it
55
+ if (typeof comparisonResult === "string") {
56
+ return NextResponse.json(JSON.parse(comparisonResult));
57
+ }
58
+
59
+ throw new Error("Unexpected comparison result format");
60
  } catch (error: any) {
61
  console.error("Compare error:", error.message);
62
  return NextResponse.json({ error: error.message || "Comparison failed" }, { status: 500 });