xy63 commited on
Commit
cf49d7a
·
verified ·
1 Parent(s): 18b5db4

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +362 -149
app.py CHANGED
@@ -14,14 +14,11 @@ from marker.logger import configure_logging
14
  from surya.settings import settings as surya_settings
15
  import traceback
16
  import re
17
- import time
18
 
19
- # =========================
20
- # Marker / PDF 预处理配置
21
- # =========================
22
  configure_logging()
23
  MAX_PAGES = 30
24
- MIN_LENGTH = 200
25
  settings.EXTRACT_IMAGES = False
26
  settings.DEBUG = False
27
  settings.PDFTEXT_CPU_WORKERS = 1
@@ -32,9 +29,7 @@ surya_settings.IN_STREAMLIT = True
32
  model_refs = load_all_models()
33
  metadata = {}
34
 
35
- # =========================
36
- # LLM 加载
37
- # =========================
38
  model_name = "maxidl/Llama-OpenReviewer-8B"
39
  model = AutoModelForCausalLM.from_pretrained(
40
  model_name,
@@ -42,48 +37,32 @@ model = AutoModelForCausalLM.from_pretrained(
42
  device_map="auto"
43
  )
44
  tokenizer = AutoTokenizer.from_pretrained(model_name)
 
45
  if tokenizer.pad_token is None:
46
  tokenizer.pad_token = tokenizer.eos_token
47
  tokenizer.pad_token_id = tokenizer.eos_token_id
 
48
 
49
- streamer = TextIteratorStreamer(
50
- tokenizer,
51
- skip_prompt=True,
52
- decode_kwargs=dict(skip_special_tokens=True)
53
- )
54
 
55
- # 允许 TF32(可选加速;不支持也不会报错)
56
  try:
57
  torch.backends.cuda.matmul.allow_tf32 = True
58
  torch.backends.cudnn.allow_tf32 = True
59
  except Exception:
60
  pass
 
61
 
62
- # =========================
63
- # 推理时长保护:输入/输出限长 + 超长先摘要
64
- # =========================
65
- MAX_INPUT_TOKENS = 3000 # 评审输入上限(按 token)
66
- MAX_NEW_TOKENS = 512 # 生成上限(按 token)
67
- FAST_SUMMARY_TOK = 480 # 超长文本的“先摘要”输出上限
68
- LONG_TEXT_THRESHOLD_TOK = 6000 # 触发“先摘要”的 token 阈值
69
-
70
- def truncate_by_tokens(text: str, tokenizer, max_tokens: int) -> str:
71
- """按 token 截断文本,优先保留头尾内容。"""
72
- ids = tokenizer(text, return_tensors="pt", add_special_tokens=False)["input_ids"][0]
73
- if ids.size(0) <= max_tokens:
74
- return text
75
- head = max_tokens // 2
76
- tail = max_tokens - head
77
- kept = torch.cat([ids[:head], ids[-tail:]], dim=0)
78
- return tokenizer.decode(kept, skip_special_tokens=True)
79
-
80
- # =========================
81
- # 轻量“是否为论文”检测
82
- # =========================
83
  try:
84
  paper_classifier = pipeline(
85
  "text-classification",
86
- model="fabriceyhc/bert-base-uncased-arxiv-classification",
87
  device=0 if torch.cuda.is_available() else -1,
88
  truncation=True,
89
  max_length=512
@@ -95,10 +74,11 @@ except Exception as e:
95
  paper_classifier = None
96
  AI_CLASSIFIER_AVAILABLE = False
97
 
 
98
  def init_zero_shot_classifier():
99
  try:
100
- from transformers import pipeline as hfpipe
101
- classifier = hfpipe(
102
  "zero-shot-classification",
103
  model="facebook/bart-large-mnli",
104
  device=0 if torch.cuda.is_available() else -1
@@ -108,62 +88,117 @@ def init_zero_shot_classifier():
108
  print(f"Could not initialize zero-shot classifier: {e}")
109
  return None
110
 
111
- zero_shot_classifier = init_zero_shot_classifier() if not AI_CLASSIFIER_AVAILABLE else None
 
 
 
 
 
 
112
 
113
  def ai_check_paper(text):
114
- """AI 模型判断是否为研究论文:(is_paper, confidence, reason)"""
115
- if not AI_CLASSIFIER_AVAILABLE and not zero_shot_classifier:
 
 
 
116
  return None, 0, "AI classifier not available"
117
-
 
118
  max_chars = 2000
119
  if len(text) > max_chars * 2:
120
  text_sample = text[:max_chars] + "\n...\n" + text[-max_chars:]
121
  else:
122
- text_sample = text[:max_chars * 2]
123
-
124
  try:
125
  if zero_shot_classifier and not paper_classifier:
 
126
  labels = [
127
- "academic research paper", "scientific article", "technical report",
128
- "business document", "news article", "blog post", "other document"
 
 
 
 
 
129
  ]
 
130
  result = zero_shot_classifier(
131
  text_sample,
132
  candidate_labels=labels,
133
  hypothesis_template="This text is a {}."
134
  )
135
- top_label = result["labels"][0]
136
- top_score = result["scores"][0]
 
 
 
137
  paper_labels = {"academic research paper", "scientific article", "technical report"}
 
138
  if top_label in paper_labels:
139
  if top_score > 0.7:
140
- return True, top_score, f"AI detected: {top_label} ({top_score:.2f})"
141
  elif top_score > 0.5:
142
- return True, 0.6, f"AI detected: likely {top_label} ({top_score:.2f})"
143
  else:
144
- return False, top_score, "AI uncertain"
145
  else:
146
- return False, 1 - top_score, f"AI: {top_label}, not a research paper"
 
147
  elif paper_classifier:
 
148
  result = paper_classifier(text_sample)[0]
149
- label = result["label"].lower()
150
- score = result["score"]
 
 
 
 
 
151
  paper_keywords = ['cs', 'math', 'physics', 'eess', 'econ', 'stat', 'q-bio']
152
- is_paper = any(k in label for k in paper_keywords)
 
153
  if is_paper:
154
- return True, score, f"AI detected paper: {label} ({score:.2f})"
155
  else:
156
- return False, 1 - score, "AI: not a research paper"
 
157
  except Exception as e:
158
  print(f"AI classification error: {e}")
159
  return None, 0, "AI classification failed"
 
160
  return None, 0, "AI check not performed"
161
 
 
162
  SYSTEM_PROMPT_TEMPLATE = """You are an expert reviewer for AI conferences. You follow best practices and review papers according to the reviewer guidelines.
163
 
164
  Reviewer guidelines:
165
- 1. Read the paper...
166
- [snip unchanged for brevity in comments]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  Your response must only contain the review in markdown format with sections as defined above.
168
  """
169
 
@@ -172,18 +207,146 @@ USER_PROMPT_TEMPLATE = """Review the following paper:
172
  {paper_text}
173
  """
174
 
 
175
  REVIEW_FIELDS = """## Summary
176
- [snip unchanged template content]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  ## Overall Justification
178
- [snip]
 
 
 
 
 
 
179
  """
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  def is_research_paper(text, use_ai=True):
182
- """规则 +(可选)AI 检测是否像论文。返回 (is_paper, confidence, reason)"""
 
 
 
 
183
  if not text or len(text) < MIN_LENGTH:
184
  return False, 0, "Text is too short to be a research paper"
185
-
186
  text_lower = text.lower()
 
 
187
  indicators = {
188
  'abstract': bool(re.search(r'\babstract\b', text_lower)),
189
  'introduction': bool(re.search(r'\bintroduction\b', text_lower)),
@@ -191,73 +354,114 @@ def is_research_paper(text, use_ai=True):
191
  'references': bool(re.search(r'\b(references|bibliography)\b', text_lower)),
192
  'methodology': bool(re.search(r'\b(method|methodology|approach|algorithm|model)\b', text_lower)),
193
  'results': bool(re.search(r'\b(results|experiments|evaluation|analysis)\b', text_lower)),
194
- 'citations': bool(re.search(r'\[[\d,\s]+\]|\(\w+,?\s*\d{4}\)', text)),
195
  'figures_tables': bool(re.search(r'\b(figure\s*\d+|table\s*\d+|fig\.\s*\d+)\b', text_lower)),
196
  'academic_terms': bool(re.search(r'\b(propose|present|demonstrate|evaluate|contribution|novel|state-of-the-art)\b', text_lower))
197
  }
 
 
198
  indicator_count = sum(indicators.values())
199
-
 
 
200
  non_paper_indicators = []
 
 
201
  if re.search(r'\b(invoice|receipt)\b', text_lower) and re.search(r'\b(total|amount|payment|billing)\b', text_lower):
202
  non_paper_indicators.append(True)
 
 
203
  if re.search(r'\bpurchase order\b', text_lower):
204
  non_paper_indicators.append(True)
 
 
205
  if re.search(r'\b(dear\s+(sir|madam|customer)|sincerely|best regards|yours truly)\b', text_lower):
206
  non_paper_indicators.append(True)
 
 
207
  if re.search(r'\b(chapter\s+\d+|lesson\s+\d+|exercise\s+\d+)\b', text_lower) and indicator_count < 3:
208
  non_paper_indicators.append(True)
 
 
209
  if re.search(r'<html|<body|<div|<script|<!DOCTYPE', text_lower):
210
  non_paper_indicators.append(True)
 
 
211
  if re.search(r'\b(ingredients|recipe|preparation|cooking time|servings)\b', text_lower) and not re.search(r'\b(algorithm|method|experiment)\b', text_lower):
212
  non_paper_indicators.append(True)
 
 
213
  if any(non_paper_indicators) and indicator_count < 6:
214
  return False, 0, "Content appears to be a non-academic document"
215
-
 
216
  ai_result = None
217
  ai_confidence = 0
218
  ai_reason = ""
219
- if use_ai and (AI_CLASSIFIER_AVAILABLE or zero_shot_classifier):
 
220
  ai_result, ai_confidence, ai_reason = ai_check_paper(text)
221
-
 
 
222
  if indicator_count == 9:
223
- rule_decision, rule_confidence = True, 0.9
224
- rule_reason = f"Found all {indicator_count}/9 academic indicators"
 
225
  elif indicator_count >= 6:
226
- rule_decision, rule_confidence = True, 0.6
 
227
  missing = [k for k, v in indicators.items() if not v]
228
- rule_reason = f"Found {indicator_count}/9 indicators. Missing: {', '.join(missing)}"
229
  else:
230
- rule_decision, rule_confidence = False, 0
 
231
  missing = [k for k, v in indicators.items() if not v]
232
  rule_reason = f"Found only {indicator_count}/9 indicators. Missing: {', '.join(missing[:4])}"
233
-
 
234
  if ai_result is not None:
 
 
235
  combined_confidence = (rule_confidence * 0.6) + (ai_confidence * 0.4)
 
 
236
  if rule_decision and ai_result:
237
- return True, (0.9 if combined_confidence >= 0.9 else 0.6), f"{rule_reason}. {ai_reason}"
 
 
 
 
238
  elif not rule_decision and not ai_result:
 
239
  return False, 0, f"Not a research paper. {rule_reason}. {ai_reason}"
240
  else:
 
241
  if combined_confidence >= 0.5:
242
  return True, 0.6, f"Mixed signals: {rule_reason}. {ai_reason}"
243
  else:
244
  return False, 0, f"Likely not a research paper. {rule_reason}. {ai_reason}"
245
  else:
 
246
  if rule_decision:
247
- return True, (0.9 if rule_confidence >= 0.9 else 0.6), rule_reason
 
 
 
248
  else:
249
  return False, 0, f"Does not appear to be a research paper. {rule_reason}"
250
 
251
  def create_messages(review_fields, paper_text):
252
- return [
253
  {"role": "system", "content": SYSTEM_PROMPT_TEMPLATE.format(review_fields=review_fields)},
254
- {"role": "user", "content": USER_PROMPT_TEMPLATE.format(paper_text=paper_text)},
255
  ]
 
256
 
257
  @spaces.GPU()
258
  def convert_file(filepath):
259
  full_text, images, out_metadata = convert_single_pdf(
260
- filepath, model_refs, metadata=metadata, max_pages=MAX_PAGES
261
  )
262
  return full_text
263
 
@@ -269,10 +473,12 @@ def process_file(file):
269
  filetype = find_filetype(filepath)
270
  if filetype == "other":
271
  raise ValueError()
 
272
  length = get_length_of_text(filepath)
273
  if length < MIN_LENGTH:
274
  raise ValueError()
275
- paper_text = convert_file(filepath).strip()
 
276
  if not len(paper_text) > MIN_LENGTH:
277
  raise ValueError()
278
  except spaces.zero.gradio.HTMLError as e:
@@ -282,33 +488,27 @@ def process_file(file):
282
  print(traceback.format_exc())
283
  print(f"Error converting {filepath}: {e}")
284
  return "Error processing pdf", False
285
-
 
286
  is_paper, confidence, reason = is_research_paper(paper_text, use_ai=True)
287
  if not is_paper:
288
- return (
289
- "⚠️ **Not a Research Paper**\n\n"
290
- "The uploaded document does not appear to be a research paper.\n\n"
291
- f"Reason: {reason}\n\n"
292
- "Please upload a proper academic/research paper with sections like Abstract, Introduction, Methodology, Results, and References.",
293
- False,
294
- )
295
  if confidence < 0.9:
296
- paper_text = (
297
- f"⚠️ **Warning**: {reason}. \n\n"
298
- "The document may be incomplete or missing key sections. Proceeding with review generation...\n\n---\n\n"
299
- f"{paper_text}"
300
- )
301
  return paper_text, True
302
 
303
- # ============== 新增:一步式端点(自动瘦身) ==============
304
  @spaces.GPU(duration=90)
305
  def review(paper_text: str, review_template: str):
306
- # 快速检查(禁用 AI,加速)
307
  is_paper, confidence, reason = is_research_paper(paper_text, use_ai=False)
308
  if not is_paper:
309
  return f"⚠️ Cannot generate review: {reason}"
310
 
311
- # 估算 token,超长则先摘要
312
  pt_ids = tokenizer(paper_text, return_tensors="pt", add_special_tokens=False)["input_ids"][0]
313
  if pt_ids.size(0) > LONG_TEXT_THRESHOLD_TOK:
314
  summary_prompt = (
@@ -316,7 +516,7 @@ def review(paper_text: str, review_template: str):
316
  )
317
  messages = [
318
  {"role": "system", "content": "You are a helpful scientific summarizer."},
319
- {"role": "user", "content": f"{summary_prompt}\n\n---\n\n{paper_text}"},
320
  ]
321
  sum_ids = tokenizer.apply_chat_template(
322
  messages, add_generation_prompt=True, return_tensors="pt"
@@ -329,17 +529,17 @@ def review(paper_text: str, review_template: str):
329
  )
330
  paper_text = tokenizer.decode(sum_out[0][sum_ids.shape[1]:], skip_special_tokens=True)
331
 
332
- # 截断评审输入,确保不超限
333
  paper_text = truncate_by_tokens(paper_text, tokenizer, MAX_INPUT_TOKENS)
334
 
335
- # 构造消息并生成(非流式,一步返回)
336
  messages = create_messages(review_template, paper_text)
337
  input_ids = tokenizer.apply_chat_template(
338
  messages, add_generation_prompt=True, return_tensors='pt'
339
  ).to(model.device)
340
- attention_mask = torch.ones_like(input_ids)
341
 
342
- out = model.generate(
 
343
  input_ids=input_ids,
344
  attention_mask=attention_mask,
345
  max_new_tokens=MAX_NEW_TOKENS,
@@ -348,34 +548,38 @@ def review(paper_text: str, review_template: str):
348
  top_p=0.9,
349
  pad_token_id=tokenizer.pad_token_id
350
  )
 
351
  return tokenizer.decode(out[0][input_ids.shape[1]:], skip_special_tokens=True).replace("<|eot_id|>", "")
 
352
 
353
- # ============== 改造:原 /generate(流式,但限长) ==============
354
  @spaces.GPU(duration=90)
355
  def generate(paper_text, review_template):
356
- is_paper, confidence, reason = is_research_paper(paper_text, use_ai=False)
 
357
  if not is_paper:
358
  return f"⚠️ Cannot generate review: {reason}"
359
 
360
- # 关键:截断输入,防止上下文超大
361
  paper_text = truncate_by_tokens(paper_text, tokenizer, MAX_INPUT_TOKENS)
362
-
363
  messages = create_messages(review_template, paper_text)
364
  input_ids = tokenizer.apply_chat_template(
365
  messages,
366
  add_generation_prompt=True,
367
  return_tensors='pt'
368
  ).to(model.device)
 
 
369
  attention_mask = torch.ones_like(input_ids)
370
-
371
  print(f"input_ids shape (truncated): {input_ids.shape}")
372
  generation_kwargs = dict(
373
  input_ids=input_ids,
374
  attention_mask=attention_mask,
375
  streamer=streamer,
376
- max_new_tokens=MAX_NEW_TOKENS, # 4096 降为 512
377
  do_sample=True,
378
- temperature=0.5,
379
  top_p=0.9,
380
  pad_token_id=tokenizer.pad_token_id
381
  )
@@ -386,10 +590,9 @@ def generate(paper_text, review_template):
386
  generated_text += new_text
387
  yield generated_text.replace("<|eot_id|>", "")
388
 
389
- # =========================
390
- # UI(保持不变,仅加 api_name)
391
- # =========================
392
- title_html = """<h1 align="center">OpenReviewer</h1>
393
  <div align="center">Using <a href="https://huggingface.co/maxidl/Llama-OpenReviewer-8B" target="_blank"><code>Llama-OpenReviewer-8B</code></a> - Built with Llama</div>
394
  """
395
 
@@ -397,36 +600,45 @@ description = """This is an online demo featuring [Llama-OpenReviewer-8B](https:
397
 
398
  ## Demo Guidelines
399
 
400
- 1. Upload your paper as a PDF file. Alternatively you can paste the full text of your paper in markdown format below...
 
 
 
 
 
 
 
 
 
 
 
401
  """
402
 
403
  theme = gr.themes.Default(primary_hue="gray", secondary_hue="blue", neutral_hue="slate")
404
  with gr.Blocks(theme=theme) as demo:
405
- gr.HTML(title_html)
406
- gr.Markdown(description)
407
-
 
408
  with gr.Row():
409
  file_input = gr.File(file_types=[".pdf"], file_count="single")
410
  validation_status = gr.Markdown("", visible=False)
411
-
412
- paper_text_field = gr.Textbox(
413
- "Upload a pdf or paste the full text of your paper in markdown format here.",
414
- label="Paper Text",
415
- lines=20, max_lines=20, autoscroll=False
416
- )
417
-
418
  with gr.Accordion("Review Template", open=False):
419
- gr.Markdown("We use the ICLR 2025 review template by default, but you can modify the template below as you like.")
420
- review_template_field = gr.Textbox(label=" ", lines=20, max_lines=20, autoscroll=False, value=REVIEW_FIELDS)
421
-
422
  generate_button = gr.Button("Generate Review", interactive=False)
423
-
424
  def handle_file_upload(file):
425
  if file is None:
426
  return "", gr.update(visible=False), gr.update(interactive=False)
427
  text, is_valid = process_file(file)
428
  if is_valid:
429
- is_paper, confidence, reason = is_research_paper(text, use_ai=False)
 
430
  if confidence >= 0.9:
431
  status_msg = "✅ **Document validated**: This appears to be a complete research paper."
432
  else:
@@ -434,48 +646,49 @@ with gr.Blocks(theme=theme) as demo:
434
  return text, gr.update(value=status_msg, visible=True), gr.update(interactive=True)
435
  else:
436
  return text, gr.update(value="❌ **Validation failed**: Please upload a research paper.", visible=True), gr.update(interactive=False)
437
-
438
  def handle_text_change(text):
439
  if not text or len(text) < 200:
440
  return gr.update(interactive=False), gr.update(visible=False)
 
441
  is_paper, confidence, reason = is_research_paper(text, use_ai=True)
442
  if is_paper:
443
- status = ("✅ **Text validated**: This appears to be a complete research paper."
444
- if confidence >= 0.9 else
445
- f"⚠️ **Warning**: {reason}\n\nThe document may be incomplete or missing key sections.")
 
 
446
  return gr.update(interactive=True), gr.update(value=status, visible=True)
447
  else:
448
  return gr.update(interactive=False), gr.update(value=f"❌ **Not a research paper**: {reason}", visible=True)
449
-
450
  file_input.upload(handle_file_upload, file_input, [paper_text_field, validation_status, generate_button])
451
  paper_text_field.change(handle_text_change, paper_text_field, [generate_button, validation_status])
452
-
453
  review_field = gr.Markdown("\n\n\n\n\n", label="Review")
454
 
455
- # 显式命名 /generate(流式)
456
  generate_button.click(
457
- fn=lambda: gr.update(interactive=False),
458
- inputs=None,
459
  outputs=generate_button
460
  ).then(
461
- generate,
462
- [paper_text_field, review_template_field],
463
  review_field,
464
- api_name="generate"
465
  ).then(
466
- fn=lambda: gr.update(interactive=True),
467
- inputs=None,
468
  outputs=generate_button
469
  )
470
 
471
- # (隐藏)显式暴露“一步式” /review 端点,供 SDK 使用
472
- gr.Button(visible=False).click(
473
- review,
474
- [paper_text_field, review_template_field],
475
- review_field,
476
- api_name="review"
477
  )
478
-
479
  demo.title = "OpenReviewer"
480
 
481
  if __name__ == "__main__":
 
14
  from surya.settings import settings as surya_settings
15
  import traceback
16
  import re
 
17
 
18
+ # marker
 
 
19
  configure_logging()
20
  MAX_PAGES = 30
21
+ MIN_LENGTH=200
22
  settings.EXTRACT_IMAGES = False
23
  settings.DEBUG = False
24
  settings.PDFTEXT_CPU_WORKERS = 1
 
29
  model_refs = load_all_models()
30
  metadata = {}
31
 
32
+ # prepare LLM
 
 
33
  model_name = "maxidl/Llama-OpenReviewer-8B"
34
  model = AutoModelForCausalLM.from_pretrained(
35
  model_name,
 
37
  device_map="auto"
38
  )
39
  tokenizer = AutoTokenizer.from_pretrained(model_name)
40
+ # Set pad_token to eos_token if not set (common for Llama models)
41
  if tokenizer.pad_token is None:
42
  tokenizer.pad_token = tokenizer.eos_token
43
  tokenizer.pad_token_id = tokenizer.eos_token_id
44
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, decode_kwargs=dict(skip_special_tokens=True))
45
 
46
+ # ======== 新增:生成与输入长度限制 / 加速开关 ========
47
+ MAX_INPUT_TOKENS = 3000 # 评审输入截断上限(可 2500~3500 之间调)
48
+ MAX_NEW_TOKENS = 512 # 生成上限(必要时可到 640/768)
49
+ FAST_SUMMARY_TOK = 480 # 超长文本“先摘要”的输出上限
50
+ LONG_TEXT_THRESHOLD_TOK = 6000 # 超过此上下文 token 视为超长,触发“先摘要”
51
 
52
+ # 允许 TF32(若硬件支持可提速;不支持也不会报错)
53
  try:
54
  torch.backends.cuda.matmul.allow_tf32 = True
55
  torch.backends.cudnn.allow_tf32 = True
56
  except Exception:
57
  pass
58
+ # ======== 新增结束 ========
59
 
60
+ # Initialize AI classifier for paper detection
61
+ # Using a lightweight model for text classification
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  try:
63
  paper_classifier = pipeline(
64
  "text-classification",
65
+ model="fabriceyhc/bert-base-uncased-arxiv-classification", # You can use other models
66
  device=0 if torch.cuda.is_available() else -1,
67
  truncation=True,
68
  max_length=512
 
74
  paper_classifier = None
75
  AI_CLASSIFIER_AVAILABLE = False
76
 
77
+ # Alternative: Use zero-shot classification (more flexible but slower)
78
  def init_zero_shot_classifier():
79
  try:
80
+ from transformers import pipeline
81
+ classifier = pipeline(
82
  "zero-shot-classification",
83
  model="facebook/bart-large-mnli",
84
  device=0 if torch.cuda.is_available() else -1
 
88
  print(f"Could not initialize zero-shot classifier: {e}")
89
  return None
90
 
91
+ # If primary classifier fails, try zero-shot
92
+ if not AI_CLASSIFIER_AVAILABLE:
93
+ zero_shot_classifier = init_zero_shot_classifier()
94
+ if zero_shot_classifier:
95
+ AI_CLASSIFIER_AVAILABLE = True
96
+ else:
97
+ zero_shot_classifier = None
98
 
99
  def ai_check_paper(text):
100
+ """
101
+ Use AI model to check if text is a research paper
102
+ Returns (is_paper, confidence, ai_reason)
103
+ """
104
+ if not AI_CLASSIFIER_AVAILABLE:
105
  return None, 0, "AI classifier not available"
106
+
107
+ # Truncate text for AI model (keep beginning and end which are most informative)
108
  max_chars = 2000
109
  if len(text) > max_chars * 2:
110
  text_sample = text[:max_chars] + "\n...\n" + text[-max_chars:]
111
  else:
112
+ text_sample = text[:max_chars*2]
113
+
114
  try:
115
  if zero_shot_classifier and not paper_classifier:
116
+ # Use zero-shot classification
117
  labels = [
118
+ "academic research paper",
119
+ "scientific article",
120
+ "technical report",
121
+ "business document",
122
+ "news article",
123
+ "blog post",
124
+ "other document"
125
  ]
126
+
127
  result = zero_shot_classifier(
128
  text_sample,
129
  candidate_labels=labels,
130
  hypothesis_template="This text is a {}."
131
  )
132
+
133
+ # Check if top labels are paper-related
134
+ top_label = result['labels'][0]
135
+ top_score = result['scores'][0]
136
+
137
  paper_labels = {"academic research paper", "scientific article", "technical report"}
138
+
139
  if top_label in paper_labels:
140
  if top_score > 0.7:
141
+ return True, top_score, f"AI detected: {top_label} (confidence: {top_score:.2f})"
142
  elif top_score > 0.5:
143
+ return True, 0.6, f"AI detected: likely {top_label} (confidence: {top_score:.2f})"
144
  else:
145
+ return False, top_score, f"AI detected: uncertain document type"
146
  else:
147
+ return False, 1-top_score, f"AI detected: {top_label}, not a research paper"
148
+
149
  elif paper_classifier:
150
+ # Use pre-trained classifier
151
  result = paper_classifier(text_sample)[0]
152
+
153
+ # Check if the label indicates it's a paper
154
+ # This depends on the specific model used
155
+ label = result['label'].lower()
156
+ score = result['score']
157
+
158
+ # Adjust based on your chosen model's labels
159
  paper_keywords = ['cs', 'math', 'physics', 'eess', 'econ', 'stat', 'q-bio']
160
+ is_paper = any(keyword in label for keyword in paper_keywords)
161
+
162
  if is_paper:
163
+ return True, score, f"AI detected: {label} paper (confidence: {score:.2f})"
164
  else:
165
+ return False, 1-score, f"AI detected: not a research paper"
166
+
167
  except Exception as e:
168
  print(f"AI classification error: {e}")
169
  return None, 0, "AI classification failed"
170
+
171
  return None, 0, "AI check not performed"
172
 
173
+ # Define prompts
174
  SYSTEM_PROMPT_TEMPLATE = """You are an expert reviewer for AI conferences. You follow best practices and review papers according to the reviewer guidelines.
175
 
176
  Reviewer guidelines:
177
+ 1. Read the paper: It's important to carefully read through the entire paper, and to look up any related work and citations that will help you comprehensively evaluate it. Be sure to give yourself sufficient time for this step.
178
+ 2. While reading, consider the following:
179
+ - Objective of the work: What is the goal of the paper? Is it to better address a known application or problem, draw attention to a new application or problem, or to introduce and/or explain a new theoretical finding? A combination of these? Different objectives will require different considerations as to potential value and impact.
180
+ - Strong points: is the submission clear, technically correct, experimentally rigorous, reproducible, does it present novel findings (e.g., theoretically, algorithmically, etc.)?
181
+ - Weak points: is it weak in any of the aspects listed in b.?
182
+ - Be mindful of potential biases and try to be open-minded about the value and interest a paper can hold for the community, even if it may not be very interesting for you.
183
+ 3. Answer four key questions for yourself, to make a recommendation to Accept or Reject:
184
+ - What is the specific question and/or problem tackled by the paper?
185
+ - Is the approach well motivated, including being well-placed in the literature?
186
+ - Does the paper support the claims? This includes determining if results, whether theoretical or empirical, are correct and if they are scientifically rigorous.
187
+ - What is the significance of the work? Does it contribute new knowledge and sufficient value to the community? Note, this does not necessarily require state-of-the-art results. Submissions bring value to the community when they convincingly demonstrate new, relevant, impactful knowledge (incl., empirical, theoretical, for practitioners, etc).
188
+ 4. Write your review including the following information:
189
+ - Summarize what the paper claims to contribute. Be positive and constructive.
190
+ - List strong and weak points of the paper. Be as comprehensive as possible.
191
+ - Clearly state your initial recommendation (accept or reject) with one or two key reasons for this choice.
192
+ - Provide supporting arguments for your recommendation.
193
+ - Ask questions you would like answered by the authors to help you clarify your understanding of the paper and provide the additional evidence you need to be confident in your assessment.
194
+ - Provide additional feedback with the aim to improve the paper. Make it clear that these points are here to help, and not necessarily part of your decision assessment.
195
+
196
+ Your write reviews in markdown format. Your reviews contain the following sections:
197
+
198
+ # Review
199
+
200
+ {review_fields}
201
+
202
  Your response must only contain the review in markdown format with sections as defined above.
203
  """
204
 
 
207
  {paper_text}
208
  """
209
 
210
+ # For now, use fixed review fields
211
  REVIEW_FIELDS = """## Summary
212
+ Briefly summarize the paper and its contributions. This is not the place to critique the paper; the authors should generally agree with a well-written summary.
213
+
214
+ ## Novelty
215
+ Please assign the paper a numerical rating on the following scale to indicate the novelty and originality of the work. Consider whether the paper presents new ideas, methods, or perspectives that have not been explored before. Choose from the following:
216
+ 4: excellent - Highly original work with groundbreaking ideas or completely novel approaches
217
+ 3: good - Significant new contributions with clear advances over existing work
218
+ 2: fair - Some new elements but largely incremental improvements or combinations of existing ideas
219
+ 1: poor - Little to no novelty, mostly reproducing existing work or trivial variations
220
+
221
+ ## Novelty Explanation
222
+ IMPORTANT: Focus ONLY on novelty aspects. DO NOT discuss soundness, presentation, or general contribution here.
223
+ Please provide specific justification for your novelty score by addressing:
224
+ - What specific new concepts, methods, or approaches does this paper introduce?
225
+ - How do these differ from existing work in the field? Cite specific prior work for comparison.
226
+ - Are the differences substantial or incremental?
227
+ - Is this addressing a problem in a genuinely new way, or applying known methods to a new domain?
228
+ DO NOT repeat content from other sections. DO NOT discuss writing quality, experimental rigor, or implementation details here.
229
+
230
+ ## Soundness
231
+ Please assign the paper a numerical rating on the following scale to indicate the soundness of the technical claims, experimental and research methodology and on whether the central claims of the paper are adequately supported with evidence. Choose from the following:
232
+ 4: excellent
233
+ 3: good
234
+ 2: fair
235
+ 1: poor
236
+
237
+ ## Soundness Explanation
238
+ IMPORTANT: Focus ONLY on technical correctness and methodological rigor. DO NOT discuss novelty or writing quality here.
239
+ Please provide specific reasons for your soundness score by addressing:
240
+ - Are the technical claims mathematically/logically correct?
241
+ - Is the experimental methodology rigorous and appropriate?
242
+ - Are the experiments sufficient to support the claims?
243
+ - Are there any methodological flaws or missing controls?
244
+ - Is the statistical analysis (if any) appropriate and correctly executed?
245
+ DO NOT repeat content from other sections. DO NOT discuss the novelty of the approach or presentation quality here.
246
+
247
+ ## Presentation
248
+ Please assign the paper a numerical rating on the following scale to indicate the quality of the presentation. This should take into account the writing style and clarity, as well as contextualization relative to prior work. Choose from the following:
249
+ 4: excellent
250
+ 3: good
251
+ 2: fair
252
+ 1: poor
253
+
254
+ ## Presentation Explanation
255
+ IMPORTANT: Focus ONLY on writing quality, clarity, and organization. DO NOT discuss technical merit or novelty here.
256
+ Please explain your presentation score by addressing:
257
+ - Is the paper well-organized and easy to follow?
258
+ - Are the main ideas clearly explained?
259
+ - Are figures, tables, and visualizations effective and well-designed?
260
+ - Is the related work section comprehensive and fair?
261
+ - Are mathematical notations consistent and clear?
262
+ - Is the language precise and grammatically correct?
263
+ DO NOT repeat content from other sections. DO NOT discuss the novelty of ideas or soundness of methods here.
264
+
265
+ ## Contribution
266
+ Please assign the paper a numerical rating on the following scale to indicate the quality of the overall contribution this paper makes to the research area being studied. Are the questions being asked important? Does the paper bring a significant originality of ideas and/or execution? Are the results valuable to share with the broader ICLR community? Choose from the following:
267
+ 4: excellent
268
+ 3: good
269
+ 2: fair
270
+ 1: poor
271
+
272
+ ## Contribution Explanation
273
+ IMPORTANT: Focus on the OVERALL IMPACT and SIGNIFICANCE to the field. This is different from novelty.
274
+ Please justify your contribution score by explaining:
275
+ - Why is this work important for the field?
276
+ - What practical or theoretical impact could this have?
277
+ - Who would benefit from this work and how?
278
+ - Does this open new research directions or close important gaps?
279
+ - How significant are the improvements over baselines (if applicable)?
280
+ Consider both immediate utility and long-term impact. DO NOT simply repeat the novelty assessment here.
281
+
282
+ ## Strengths
283
+ List the main strengths of the paper. Be specific and provide evidence. Each strength should be a separate bullet point. Focus on what the paper does well across all dimensions (novelty, soundness, presentation, contribution). Avoid generic statements.
284
+
285
+ ## Weaknesses
286
+ List the main weaknesses of the paper. Be specific, constructive, and actionable. Each weakness should be a separate bullet point with suggestions for improvement where possible. Focus on significant issues that affect the paper's validity or impact.
287
+
288
+ ## Questions
289
+ List specific questions for the authors that could clarify ambiguities or address concerns. Number each question. These should be questions where the answer could potentially change your assessment of the paper.
290
+
291
+ ## Flag For Ethics Review
292
+ If there are ethical issues with this paper, please flag the paper for an ethics review and select area of expertise that would be most useful for the ethics reviewer to have. Please select all that apply. Choose from the following:
293
+ No ethics review needed.
294
+ Yes, Discrimination / bias / fairness concerns
295
+ Yes, Privacy, security and safety
296
+ Yes, Legal compliance (e.g., GDPR, copyright, terms of use)
297
+ Yes, Potentially harmful insights, methodologies and applications
298
+ Yes, Responsible research practice (e.g., human subjects, data release)
299
+ Yes, Research integrity issues (e.g., plagiarism, dual submission)
300
+ Yes, Unprofessional behaviors (e.g., unprofessional exchange between authors and reviewers)
301
+ Yes, Other reasons (please specify below)
302
+
303
+ ## Details Of Ethics Concerns
304
+ Please provide details of your concerns. If no ethics review is needed, write "N/A".
305
+
306
+ ## Rating
307
+ Please provide an "overall score" for this submission. Choose from the following:
308
+ 1: strong reject
309
+ 3: reject, not good enough
310
+ 5: marginally below the acceptance threshold
311
+ 6: marginally above the acceptance threshold
312
+ 8: accept, good paper
313
+ 10: strong accept, should be highlighted at the conference
314
+
315
  ## Overall Justification
316
+ Provide a comprehensive justification for your overall rating that:
317
+ - Synthesizes the assessments from all dimensions (novelty, soundness, presentation, contribution)
318
+ - Explains how you weighted different aspects in arriving at your final score
319
+ - Clearly states whether the strengths outweigh the weaknesses or vice versa
320
+ - Indicates what would need to change for a different rating
321
+ This should be a holistic assessment, not a repetition of individual sections.
322
+
323
  """
324
 
325
+ # ======== 新增:token 级截断 ========
326
+ def truncate_by_tokens(text: str, tokenizer, max_tokens: int) -> str:
327
+ """按 token 截断文本,优先保留开头+结尾(论文信息密度最高的区域)"""
328
+ ids = tokenizer(text, return_tensors="pt", add_special_tokens=False)["input_ids"][0]
329
+ if ids.size(0) <= max_tokens:
330
+ return text
331
+ head = max_tokens // 2
332
+ tail = max_tokens - head
333
+ kept = torch.cat([ids[:head], ids[-tail:]], dim=0)
334
+ return tokenizer.decode(kept, skip_special_tokens=True)
335
+ # ======== 新增结束 ========
336
+
337
+ # Enhanced function that combines rule-based and AI checks
338
  def is_research_paper(text, use_ai=True):
339
+ """
340
+ Check if the given text appears to be a research paper.
341
+ Combines rule-based detection with AI classification.
342
+ Returns (is_paper, confidence, reason)
343
+ """
344
  if not text or len(text) < MIN_LENGTH:
345
  return False, 0, "Text is too short to be a research paper"
346
+
347
  text_lower = text.lower()
348
+
349
+ # Academic paper indicators (must have multiple)
350
  indicators = {
351
  'abstract': bool(re.search(r'\babstract\b', text_lower)),
352
  'introduction': bool(re.search(r'\bintroduction\b', text_lower)),
 
354
  'references': bool(re.search(r'\b(references|bibliography)\b', text_lower)),
355
  'methodology': bool(re.search(r'\b(method|methodology|approach|algorithm|model)\b', text_lower)),
356
  'results': bool(re.search(r'\b(results|experiments|evaluation|analysis)\b', text_lower)),
357
+ 'citations': bool(re.search(r'\[[\d,\s]+\]|\(\w+,?\s*\d{4}\)', text)), # [1,2,3] or (Author, 2024)
358
  'figures_tables': bool(re.search(r'\b(figure\s*\d+|table\s*\d+|fig\.\s*\d+)\b', text_lower)),
359
  'academic_terms': bool(re.search(r'\b(propose|present|demonstrate|evaluate|contribution|novel|state-of-the-art)\b', text_lower))
360
  }
361
+
362
+ # Count how many indicators are present
363
  indicator_count = sum(indicators.values())
364
+
365
+ # Check for non-paper content with context
366
+ # Only flag as non-paper if these terms appear WITHOUT academic context
367
  non_paper_indicators = []
368
+
369
+ # Check for invoice/receipt patterns (multiple commercial terms together)
370
  if re.search(r'\b(invoice|receipt)\b', text_lower) and re.search(r'\b(total|amount|payment|billing)\b', text_lower):
371
  non_paper_indicators.append(True)
372
+
373
+ # Check for purchase order specifically (not just "purchase")
374
  if re.search(r'\bpurchase order\b', text_lower):
375
  non_paper_indicators.append(True)
376
+
377
+ # Check for letter format
378
  if re.search(r'\b(dear\s+(sir|madam|customer)|sincerely|best regards|yours truly)\b', text_lower):
379
  non_paper_indicators.append(True)
380
+
381
+ # Check for textbook structure
382
  if re.search(r'\b(chapter\s+\d+|lesson\s+\d+|exercise\s+\d+)\b', text_lower) and indicator_count < 3:
383
  non_paper_indicators.append(True)
384
+
385
+ # Check for HTML/web content
386
  if re.search(r'<html|<body|<div|<script|<!DOCTYPE', text_lower):
387
  non_paper_indicators.append(True)
388
+
389
+ # Check for recipe/cooking content
390
  if re.search(r'\b(ingredients|recipe|preparation|cooking time|servings)\b', text_lower) and not re.search(r'\b(algorithm|method|experiment)\b', text_lower):
391
  non_paper_indicators.append(True)
392
+
393
+ # If we have strong non-paper indicators AND weak academic indicators, it's not a paper
394
  if any(non_paper_indicators) and indicator_count < 6:
395
  return False, 0, "Content appears to be a non-academic document"
396
+
397
+ # Get AI assessment if available and requested
398
  ai_result = None
399
  ai_confidence = 0
400
  ai_reason = ""
401
+
402
+ if use_ai and AI_CLASSIFIER_AVAILABLE:
403
  ai_result, ai_confidence, ai_reason = ai_check_paper(text)
404
+
405
+ # Combine rule-based and AI assessments
406
+ # Rule-based decision logic
407
  if indicator_count == 9:
408
+ rule_decision = True
409
+ rule_confidence = 0.9
410
+ rule_reason = f"Found all {indicator_count}/9 academic paper indicators"
411
  elif indicator_count >= 6:
412
+ rule_decision = True
413
+ rule_confidence = 0.6
414
  missing = [k for k, v in indicators.items() if not v]
415
+ rule_reason = f"Found only {indicator_count}/9 indicators. Missing: {', '.join(missing)}"
416
  else:
417
+ rule_decision = False
418
+ rule_confidence = 0
419
  missing = [k for k, v in indicators.items() if not v]
420
  rule_reason = f"Found only {indicator_count}/9 indicators. Missing: {', '.join(missing[:4])}"
421
+
422
+ # Combine decisions
423
  if ai_result is not None:
424
+ # Both methods available - combine them
425
+ # Weight: 60% rule-based, 40% AI
426
  combined_confidence = (rule_confidence * 0.6) + (ai_confidence * 0.4)
427
+
428
+ # Decision logic
429
  if rule_decision and ai_result:
430
+ # Both agree it's a paper
431
+ if combined_confidence >= 0.9:
432
+ return True, 0.9, f"High confidence: {rule_reason}. {ai_reason}"
433
+ else:
434
+ return True, 0.6, f"Warning: {rule_reason}. {ai_reason}"
435
  elif not rule_decision and not ai_result:
436
+ # Both agree it's not a paper
437
  return False, 0, f"Not a research paper. {rule_reason}. {ai_reason}"
438
  else:
439
+ # Disagreement - use weighted decision
440
  if combined_confidence >= 0.5:
441
  return True, 0.6, f"Mixed signals: {rule_reason}. {ai_reason}"
442
  else:
443
  return False, 0, f"Likely not a research paper. {rule_reason}. {ai_reason}"
444
  else:
445
+ # Only rule-based available
446
  if rule_decision:
447
+ if rule_confidence >= 0.9:
448
+ return True, 0.9, f"High confidence: {rule_reason}"
449
+ else:
450
+ return True, 0.6, f"Warning: {rule_reason}"
451
  else:
452
  return False, 0, f"Does not appear to be a research paper. {rule_reason}"
453
 
454
  def create_messages(review_fields, paper_text):
455
+ messages = [
456
  {"role": "system", "content": SYSTEM_PROMPT_TEMPLATE.format(review_fields=review_fields)},
457
+ {"role": "user", "content": USER_PROMPT_TEMPLATE.format(paper_text=paper_text)},
458
  ]
459
+ return messages
460
 
461
  @spaces.GPU()
462
  def convert_file(filepath):
463
  full_text, images, out_metadata = convert_single_pdf(
464
+ filepath, model_refs, metadata=metadata, max_pages=MAX_PAGES
465
  )
466
  return full_text
467
 
 
473
  filetype = find_filetype(filepath)
474
  if filetype == "other":
475
  raise ValueError()
476
+
477
  length = get_length_of_text(filepath)
478
  if length < MIN_LENGTH:
479
  raise ValueError()
480
+ paper_text = convert_file(filepath)
481
+ paper_text = paper_text.strip()
482
  if not len(paper_text) > MIN_LENGTH:
483
  raise ValueError()
484
  except spaces.zero.gradio.HTMLError as e:
 
488
  print(traceback.format_exc())
489
  print(f"Error converting {filepath}: {e}")
490
  return "Error processing pdf", False
491
+
492
+ # Check if it's a research paper (with AI)
493
  is_paper, confidence, reason = is_research_paper(paper_text, use_ai=True)
494
  if not is_paper:
495
+ return f"⚠️ **Not a Research Paper**\n\nThe uploaded document does not appear to be a research paper.\n\nReason: {reason}\n\nPlease upload a proper academic/research paper with sections like Abstract, Introduction, Methodology, Results, and References.", False
496
+
497
+ # If confidence is low (6-8 indicators), add a warning
 
 
 
 
498
  if confidence < 0.9:
499
+ paper_text = f"⚠️ **Warning**: {reason}. \n\nThe document may be incomplete or missing key sections. Proceeding with review generation...\n\n---\n\n{paper_text}"
500
+
 
 
 
501
  return paper_text, True
502
 
503
+ # ======== 新增:一步式评审端点(自动“先摘要后评审” + 截断) ========
504
  @spaces.GPU(duration=90)
505
  def review(paper_text: str, review_template: str):
506
+ # Quick paper check(不用 AI 分类,加速)
507
  is_paper, confidence, reason = is_research_paper(paper_text, use_ai=False)
508
  if not is_paper:
509
  return f"⚠️ Cannot generate review: {reason}"
510
 
511
+ # 检测是否超长,必要时先摘要
512
  pt_ids = tokenizer(paper_text, return_tensors="pt", add_special_tokens=False)["input_ids"][0]
513
  if pt_ids.size(0) > LONG_TEXT_THRESHOLD_TOK:
514
  summary_prompt = (
 
516
  )
517
  messages = [
518
  {"role": "system", "content": "You are a helpful scientific summarizer."},
519
+ {"role": "user", "content": f"{summary_prompt}\n\n---\n\n{paper_text}"}
520
  ]
521
  sum_ids = tokenizer.apply_chat_template(
522
  messages, add_generation_prompt=True, return_tensors="pt"
 
529
  )
530
  paper_text = tokenizer.decode(sum_out[0][sum_ids.shape[1]:], skip_special_tokens=True)
531
 
532
+ # 截断评审输入,保证不会过长
533
  paper_text = truncate_by_tokens(paper_text, tokenizer, MAX_INPUT_TOKENS)
534
 
535
+ # 构建评审消息并一次性生成(非流式,适合 API 调用)
536
  messages = create_messages(review_template, paper_text)
537
  input_ids = tokenizer.apply_chat_template(
538
  messages, add_generation_prompt=True, return_tensors='pt'
539
  ).to(model.device)
 
540
 
541
+ attention_mask = torch.ones_like(input_ids)
542
+ generation_kwargs = dict(
543
  input_ids=input_ids,
544
  attention_mask=attention_mask,
545
  max_new_tokens=MAX_NEW_TOKENS,
 
548
  top_p=0.9,
549
  pad_token_id=tokenizer.pad_token_id
550
  )
551
+ out = model.generate(**generation_kwargs)
552
  return tokenizer.decode(out[0][input_ids.shape[1]:], skip_special_tokens=True).replace("<|eot_id|>", "")
553
+ # ======== 新增结束 ========
554
 
 
555
  @spaces.GPU(duration=90)
556
  def generate(paper_text, review_template):
557
+ # Final check before generation
558
+ is_paper, confidence, reason = is_research_paper(paper_text, use_ai=False) # Quick check without AI
559
  if not is_paper:
560
  return f"⚠️ Cannot generate review: {reason}"
561
 
562
+ # ——关键:先按 token 截断输入,避免上下文超大——
563
  paper_text = truncate_by_tokens(paper_text, tokenizer, MAX_INPUT_TOKENS)
564
+
565
  messages = create_messages(review_template, paper_text)
566
  input_ids = tokenizer.apply_chat_template(
567
  messages,
568
  add_generation_prompt=True,
569
  return_tensors='pt'
570
  ).to(model.device)
571
+
572
+ # Create attention mask
573
  attention_mask = torch.ones_like(input_ids)
574
+
575
  print(f"input_ids shape (truncated): {input_ids.shape}")
576
  generation_kwargs = dict(
577
  input_ids=input_ids,
578
  attention_mask=attention_mask,
579
  streamer=streamer,
580
+ max_new_tokens=MAX_NEW_TOKENS, # from 4096 -> 512
581
  do_sample=True,
582
+ temperature=0.5, # 略降温度,更稳更短
583
  top_p=0.9,
584
  pad_token_id=tokenizer.pad_token_id
585
  )
 
590
  generated_text += new_text
591
  yield generated_text.replace("<|eot_id|>", "")
592
 
593
+ # UI code remains exactly the same...
594
+ # ui
595
+ title = """<h1 align="center">OpenReviewer</h1>
 
596
  <div align="center">Using <a href="https://huggingface.co/maxidl/Llama-OpenReviewer-8B" target="_blank"><code>Llama-OpenReviewer-8B</code></a> - Built with Llama</div>
597
  """
598
 
 
600
 
601
  ## Demo Guidelines
602
 
603
+ 1. Upload your paper as a PDF file. Alternatively you can paste the full text of your paper in markdown format below. We do **not** store your data. User data is kept in ephemeral storage during processing.
604
+
605
+ 2. Once you upload a PDF, it will be converted to markdown and **validated to ensure it's a research paper**. This takes some time as it runs multiple transformer models to parse the layout and extract text and tables. Check out [marker](https://github.com/VikParuchuri/marker/tree/master) for details.
606
+
607
+ 3. Having obtained a markdown version of your paper and confirmed it's a valid research paper, you can now click *Generate Review*.
608
+
609
+ Take a look at the Review Template to properly interpret the generated review. You can also change the review template before generating in case you want to generate a review with a different schema and aspects.
610
+
611
+ To obtain more than one review, just generate again.
612
+
613
+ **GPU quota:** If exceeded, either sign in with your HF account or come back later. Your quota has a half-life of 2 hours.
614
+
615
  """
616
 
617
  theme = gr.themes.Default(primary_hue="gray", secondary_hue="blue", neutral_hue="slate")
618
  with gr.Blocks(theme=theme) as demo:
619
+ title = gr.HTML(title)
620
+ description = gr.Markdown(description)
621
+
622
+ # Add paper validation status
623
  with gr.Row():
624
  file_input = gr.File(file_types=[".pdf"], file_count="single")
625
  validation_status = gr.Markdown("", visible=False)
626
+
627
+ paper_text_field = gr.Textbox("Upload a pdf or paste the full text of your paper in markdown format here.", label="Paper Text", lines=20, max_lines=20, autoscroll=False)
628
+
 
 
 
 
629
  with gr.Accordion("Review Template", open=False):
630
+ review_template_description = gr.Markdown("We use the ICLR 2025 review template by default, but you can modify the template below as you like.")
631
+ review_template_field = gr.Textbox(label=" ",lines=20, max_lines=20, autoscroll=False, value=REVIEW_FIELDS)
632
+
633
  generate_button = gr.Button("Generate Review", interactive=False)
634
+
635
  def handle_file_upload(file):
636
  if file is None:
637
  return "", gr.update(visible=False), gr.update(interactive=False)
638
  text, is_valid = process_file(file)
639
  if is_valid:
640
+ # Check confidence level for appropriate message
641
+ is_paper, confidence, reason = is_research_paper(text, use_ai=False) # Quick check for display
642
  if confidence >= 0.9:
643
  status_msg = "✅ **Document validated**: This appears to be a complete research paper."
644
  else:
 
646
  return text, gr.update(value=status_msg, visible=True), gr.update(interactive=True)
647
  else:
648
  return text, gr.update(value="❌ **Validation failed**: Please upload a research paper.", visible=True), gr.update(interactive=False)
649
+
650
  def handle_text_change(text):
651
  if not text or len(text) < 200:
652
  return gr.update(interactive=False), gr.update(visible=False)
653
+
654
  is_paper, confidence, reason = is_research_paper(text, use_ai=True)
655
  if is_paper:
656
+ if confidence >= 0.9:
657
+ status = "✅ **Text validated**: This appears to be a complete research paper."
658
+ else:
659
+ # confidence < 0.9 means warning (6-8 indicators)
660
+ status = f"⚠️ **Warning**: {reason}\n\nThe document may be incomplete or missing key sections."
661
  return gr.update(interactive=True), gr.update(value=status, visible=True)
662
  else:
663
  return gr.update(interactive=False), gr.update(value=f"❌ **Not a research paper**: {reason}", visible=True)
664
+
665
  file_input.upload(handle_file_upload, file_input, [paper_text_field, validation_status, generate_button])
666
  paper_text_field.change(handle_text_change, paper_text_field, [generate_button, validation_status])
667
+
668
  review_field = gr.Markdown("\n\n\n\n\n", label="Review")
669
 
670
+ # 生成按钮(流式),显式命名 /generate
671
  generate_button.click(
672
+ fn=lambda: gr.update(interactive=False),
673
+ inputs=None,
674
  outputs=generate_button
675
  ).then(
676
+ generate,
677
+ [paper_text_field, review_template_field],
678
  review_field,
679
+ api_name="generate" # 显式命名
680
  ).then(
681
+ fn=lambda: gr.update(interactive=True),
682
+ inputs=None,
683
  outputs=generate_button
684
  )
685
 
686
+ # (可选)隐藏按钮以暴露“一步式” /review 端点,方便 SDK 直接调用
687
+ dummy = gr.Button(visible=False)
688
+ dummy.click(
689
+ review, [paper_text_field, review_template_field], review_field, api_name="review"
 
 
690
  )
691
+
692
  demo.title = "OpenReviewer"
693
 
694
  if __name__ == "__main__":