Sa-m commited on
Commit
af6c8fc
·
verified ·
1 Parent(s): a7770f6

Upload folder using huggingface_hub

Browse files
Files changed (5) hide show
  1. .gitignore +22 -0
  2. README.md +31 -32
  3. index.html +1 -1
  4. main.py +32 -23
  5. script.js +13 -2
.gitignore ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables
2
+ .env
3
+ .env.*
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.pyc
8
+ *.pyo
9
+ *.pyd
10
+ .Python
11
+ env/
12
+ venv/
13
+ .venv/
14
+
15
+ # Wrangler / Cloudflare
16
+ .wrangler/
17
+
18
+ # IDE / System
19
+ .vscode/
20
+ .idea/
21
+ .DS_Store
22
+ Thumbs.db
README.md CHANGED
@@ -1,33 +1,32 @@
1
- ---
2
- title: Manifesto Explainer
3
- emoji:
4
- colorFrom: indigo
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- app_port: 8080
9
- license: mit
10
- ---
11
-
12
- # Manifesto Explainer Backend
13
-
14
- This is the FastAPI backend for the Manifesto Explainer platform.
15
- It provides endpoints for PDF parsing, LLM summarization, and political analysis.
16
-
17
- ## Deployment on Hugging Face Spaces
18
-
19
- 1. Create a new **Space** on Hugging Face.
20
- 2. Select **Docker** as the SDK.
21
- 3. Upload all files from this repository (or push via Git).
22
- 4. Go to **Settings** > **Variables and secrets**.
23
- 5. Add your `GROQ_API_KEY` and `GEMINI_API_KEY` as **Secrets**.
24
- 6. The app will automatically build and run on port 8080.
25
-
26
- ## Integration with Frontend
27
-
28
- If you are hosting the frontend on Cloudflare Pages:
29
- 1. Copy the URL of your Hugging Face Space (e.g., `https://samarth-manifesto-explainer.hf.space`).
30
- 2. In your `index.html` on Cloudflare, add this script tag before `script.js`:
31
- ```html
32
- <script>window.BACKEND_URL = "https://your-space-name.hf.space";</script>
33
  ```
 
1
+ ---
2
+ title: Manifesto Explainer API
3
+ emoji: 🗳️
4
+ colorFrom: indigo
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 8080
9
+ ---
10
+
11
+ # Manifesto Explainer Backend
12
+
13
+ This is the FastAPI backend for the Manifesto Explainer platform.
14
+ It provides endpoints for PDF parsing, LLM summarization, and political analysis.
15
+
16
+ ## Deployment on Hugging Face Spaces
17
+
18
+ 1. Create a new **Space** on Hugging Face.
19
+ 2. Select **Docker** as the SDK.
20
+ 3. Upload all files from this repository (or push via Git).
21
+ 4. Go to **Settings** > **Variables and secrets**.
22
+ 5. Add your `GROQ_API_KEY` and `GEMINI_API_KEY` as **Secrets**.
23
+ 6. The app will automatically build and run on port 8080.
24
+
25
+ ## Integration with Frontend
26
+
27
+ If you are hosting the frontend on Cloudflare Pages:
28
+ 1. Copy the URL of your Hugging Face Space (e.g., `https://samarth-manifesto-explainer.hf.space`).
29
+ 2. In your `index.html` on Cloudflare, add this script tag before `script.js`:
30
+ ```html
31
+ <script>window.BACKEND_URL = "https://your-space-name.hf.space";</script>
 
32
  ```
index.html CHANGED
@@ -109,7 +109,7 @@
109
  </div>
110
 
111
  <!-- If deploying frontend separately (e.g. Cloudflare), set your backend URL here -->
112
- <script>window.BACKEND_URL = "";</script>
113
  <script src="script.js"></script>
114
  </body>
115
  </html>
 
109
  </div>
110
 
111
  <!-- If deploying frontend separately (e.g. Cloudflare), set your backend URL here -->
112
+ <script>window.BACKEND_URL = "https://sa-m-manifesto-explainer-cc1d45d.hf.space";</script>
113
  <script src="script.js"></script>
114
  </body>
115
  </html>
main.py CHANGED
@@ -83,31 +83,27 @@ class FailoverLLMClient:
83
  return None, ""
84
 
85
  def _get_groq_backup(self):
86
- if self.groq_client: return self.groq_client, "gemma2-9b-it"
87
  return None, ""
88
 
89
  def _call_gemini(self, messages: list, temperature: float, max_tokens: int):
90
  if not self.gemini_client: return None
91
- try:
92
- from google.genai import types
93
- prompt = ""
94
- system_instruction = "You are an expert political science analyst."
95
- for msg in messages:
96
- if msg["role"] == "system": system_instruction = msg["content"]
97
- elif msg["role"] == "user": prompt = msg["content"]
98
- response = self.gemini_client.models.generate_content(
99
- model='gemini-2.5-flash',
100
- contents=prompt,
101
- config=types.GenerateContentConfig(
102
- system_instruction=system_instruction,
103
- temperature=temperature,
104
- max_output_tokens=max_tokens
105
- )
106
  )
107
- return response.text
108
- except Exception as e:
109
- print(f"Gemini API error: {e}")
110
- return None
111
 
112
  def _call_groq_with_retry(self, model, messages, temperature, max_tokens, retries=2):
113
  import time
@@ -118,7 +114,7 @@ class FailoverLLMClient:
118
  "messages": messages,
119
  }
120
  if "gpt-oss" in model or "llama-4-scout" in model:
121
- params["max_completion_tokens"] = 16000
122
  if "gpt-oss" in model:
123
  params["reasoning_effort"] = "medium"
124
  else:
@@ -252,10 +248,23 @@ def preprocess(textParty):
252
  def generate_summary(text):
253
  available, status = llm_client.is_available()
254
  if not available: return f"Summarization is not available. (Status: {status})"
255
- if len(text) > 15000: text = text[:15000]
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  messages = [
257
  {"role": "system", "content": "You are an expert political science analyst. Provide a comprehensive, objective, and structured summary of the political manifesto. Use Markdown with headings, bullet points, and bold text for key terms. Do not stop abruptly; ensure your response is complete."},
258
- {"role": "user", "content": f"Summarize this political manifesto, focusing on main policy areas, promises, and core themes. Aim for 300-500 words.\n\nManifesto Text:\n{text}"}
259
  ]
260
  content, provider = llm_client.chat_completion(messages=messages, temperature=0.3, max_tokens=2500)
261
  return content if content else f"Error generating summary: {provider}"
 
83
  return None, ""
84
 
85
  def _get_groq_backup(self):
86
+ if self.groq_client: return self.groq_client, "llama-3.1-8b-instant"
87
  return None, ""
88
 
89
  def _call_gemini(self, messages: list, temperature: float, max_tokens: int):
90
  if not self.gemini_client: return None
91
+ from google.genai import types
92
+ prompt = ""
93
+ system_instruction = "You are an expert political science analyst."
94
+ for msg in messages:
95
+ if msg["role"] == "system": system_instruction = msg["content"]
96
+ elif msg["role"] == "user": prompt = msg["content"]
97
+ response = self.gemini_client.models.generate_content(
98
+ model='gemini-2.5-flash',
99
+ contents=prompt,
100
+ config=types.GenerateContentConfig(
101
+ system_instruction=system_instruction,
102
+ temperature=temperature,
103
+ max_output_tokens=max_tokens
 
 
104
  )
105
+ )
106
+ return response.text
 
 
107
 
108
  def _call_groq_with_retry(self, model, messages, temperature, max_tokens, retries=2):
109
  import time
 
114
  "messages": messages,
115
  }
116
  if "gpt-oss" in model or "llama-4-scout" in model:
117
+ params["max_completion_tokens"] = 8192
118
  if "gpt-oss" in model:
119
  params["reasoning_effort"] = "medium"
120
  else:
 
248
  def generate_summary(text):
249
  available, status = llm_client.is_available()
250
  if not available: return f"Summarization is not available. (Status: {status})"
251
+
252
+ # Smart sampling for large documents: Take chunks from start, middle, and end
253
+ # to ensure the summary covers the entire manifesto.
254
+ full_len = len(text)
255
+ if full_len > 18000:
256
+ chunk_size = 6000
257
+ start_chunk = text[:chunk_size]
258
+ mid_point = full_len // 2
259
+ mid_chunk = text[mid_point - (chunk_size // 2) : mid_point + (chunk_size // 2)]
260
+ end_chunk = text[-chunk_size:]
261
+ text_to_analyze = f"[BEGINNING]\n{start_chunk}\n\n[MIDDLE]\n{mid_chunk}\n\n[END]\n{end_chunk}"
262
+ else:
263
+ text_to_analyze = text
264
+
265
  messages = [
266
  {"role": "system", "content": "You are an expert political science analyst. Provide a comprehensive, objective, and structured summary of the political manifesto. Use Markdown with headings, bullet points, and bold text for key terms. Do not stop abruptly; ensure your response is complete."},
267
+ {"role": "user", "content": f"Summarize this political manifesto, focusing on main policy areas, promises, and core themes. Aim for 300-500 words.\n\nManifesto Text:\n{text_to_analyze}"}
268
  ]
269
  content, provider = llm_client.chat_completion(messages=messages, temperature=0.3, max_tokens=2500)
270
  return content if content else f"Error generating summary: {provider}"
script.js CHANGED
@@ -94,9 +94,20 @@ document.addEventListener('DOMContentLoaded', () => {
94
  body: formData
95
  });
96
 
 
97
  if (!response.ok) {
98
- const errorData = await response.json();
99
- throw new Error(errorData.detail || 'Analysis failed');
 
 
 
 
 
 
 
 
 
 
100
  }
101
 
102
  const data = await response.json();
 
94
  body: formData
95
  });
96
 
97
+ const contentType = response.headers.get("content-type");
98
  if (!response.ok) {
99
+ if (contentType && contentType.includes("application/json")) {
100
+ const errorData = await response.json();
101
+ throw new Error(errorData.detail || 'Analysis failed');
102
+ } else {
103
+ const errorText = await response.text();
104
+ console.error("Backend Error (Non-JSON):", errorText);
105
+ throw new Error(`Server Error (${response.status}). The backend might still be starting up or is misconfigured.`);
106
+ }
107
+ }
108
+
109
+ if (!contentType || !contentType.includes("application/json")) {
110
+ throw new Error("Invalid response from server. Expected JSON but received something else. Check if the Backend URL is correct.");
111
  }
112
 
113
  const data = await response.json();