Spaces:
Running
Running
Upload 2 files
Browse files
app.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
| 3 |
from core.meta_extractor import extract_golden_metadata
|
| 4 |
from core.rag_engine import initialize_rag
|
| 5 |
from core.qualitative_engine import get_qualitative_engine
|
|
|
|
| 6 |
import streamlit as st
|
| 7 |
|
| 8 |
st.set_page_config(
|
|
@@ -24,14 +25,12 @@ import zipfile
|
|
| 24 |
import math
|
| 25 |
import json
|
| 26 |
from datetime import datetime, timezone, timedelta
|
| 27 |
-
import pandas as pd
|
| 28 |
-
import numpy as np
|
| 29 |
import threading
|
| 30 |
from concurrent.futures import ThreadPoolExecutor
|
| 31 |
from streamlit.runtime.scriptrunner import add_script_run_ctx, get_script_run_ctx
|
| 32 |
|
| 33 |
from config import APP_CONFIG, AMJ_PROMPTS, ASQ_PROMPTS, GENERIC_PROMPTS
|
| 34 |
-
from utils.file_parser import extract_text_from_files
|
| 35 |
from utils.docx_builder import text_to_docx
|
| 36 |
from core.pipeline_agent import SSciPipelineAgent
|
| 37 |
from managed_auth import require_alipay_login, render_logged_in_account
|
|
@@ -124,90 +123,62 @@ def get_global_lock():
|
|
| 124 |
|
| 125 |
global_lock = get_global_lock()
|
| 126 |
|
| 127 |
-
def
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
df = pd.DataFrame(data)
|
| 185 |
-
validity_df = calculate_validity_metrics(df, constructs_info, true_loadings)
|
| 186 |
-
mean_scores = pd.DataFrame({c['name']: df[c['items']].mean(axis=1) for c in constructs_info})
|
| 187 |
-
fl_matrix = mean_scores.corr().astype(object)
|
| 188 |
-
|
| 189 |
-
for i, row in validity_df.iterrows():
|
| 190 |
-
c_name = row['Construct']
|
| 191 |
-
sqrt_ave = np.sqrt(row['AVE'])
|
| 192 |
-
fl_matrix.loc[c_name, c_name] = f"({sqrt_ave:.3f})"
|
| 193 |
-
|
| 194 |
-
for i in range(len(fl_matrix.columns)):
|
| 195 |
-
for j in range(i + 1, len(fl_matrix.columns)):
|
| 196 |
-
fl_matrix.iloc[i, j] = ""
|
| 197 |
-
|
| 198 |
-
desc_stats = mean_scores.mean().to_frame('Mean').join(mean_scores.std().to_frame('SD'))
|
| 199 |
-
return df, validity_df, desc_stats.join(fl_matrix)
|
| 200 |
-
|
| 201 |
-
def resolve_empirical_data(uploaded_files=None, language="zh"):
|
| 202 |
-
if uploaded_files:
|
| 203 |
-
user_md = extract_data_from_files(uploaded_files)
|
| 204 |
-
if user_md:
|
| 205 |
-
return None, user_md, tr(language, "actual_stats_parsed")
|
| 206 |
-
|
| 207 |
-
df, validity_df, fl_matrix_df = generate_empirical_data_robust()
|
| 208 |
-
simulated_md_prompt = f"""### Table 1: Reliability and Convergent Validity\n{validity_df.to_markdown(index=False)}\n### Table 2: Descriptive Statistics and Discriminant Validity (Fornell-Larcker Criterion)\n*Note: Diagonal elements in parentheses are the square roots of AVE.*\n{fl_matrix_df.to_markdown()}\n[STRICT EMPIRICAL INSTRUCTION]\n1. ZERO HALLUCINATION: You MUST report the exact metrics (Cronbach's α, CR, AVE, correlations) provided in the tables above.\n2. VALIDITY DEFENSE: Explicitly state that Convergent Validity is established because all AVEs > 0.5 and CRs > 0.7.\n3. DISCRIMINANT DEFENSE: Explicitly state that Discriminant Validity is satisfied per the Fornell-Larcker criterion, as the square root of AVE (diagonal values) exceed inter-construct correlations."""
|
| 209 |
-
status_msg = tr(language, "synthetic_stats_generated")
|
| 210 |
-
return df, simulated_md_prompt, status_msg
|
| 211 |
|
| 212 |
def _translation_system_prompt(target_lang):
|
| 213 |
common = """Translate the supplied academic manuscript faithfully. Preserve every citation tag, number, percentage, coefficient, p-value, confidence interval, DOI, table structure, heading level, hypothesis label, variable abbreviation, and conclusion direction. Do not add, remove, strengthen, or weaken empirical claims. Use concise, accurate, and rigorous academic language. Do not use metaphors, analogies, rhetorical questions, promotional language, formulaic transitions, or mechanical repetition. Output only the translated manuscript."""
|
|
@@ -221,7 +192,14 @@ def _translation_system_prompt(target_lang):
|
|
| 221 |
def async_translate_task(agent, en_text, placeholder, target_lang, quarantine_flag, interface_lang):
|
| 222 |
try:
|
| 223 |
sys_p = _translation_system_prompt(target_lang)
|
| 224 |
-
trans_text = agent.call_deepseek(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
trans_resolved = agent.resolve_citations(trans_text, quarantine=quarantine_flag)
|
| 226 |
placeholder.markdown(trans_resolved)
|
| 227 |
return trans_resolved
|
|
@@ -604,6 +582,7 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 604 |
SAFE_CHUNK_WORDS = agent.chunk_words_cyrillic if target_lang in ["Russian (Русский)", "Kazakh (Қазақша)"] else agent.chunk_words_default
|
| 605 |
|
| 606 |
qual_engine = get_qualitative_engine(agent)
|
|
|
|
| 607 |
|
| 608 |
try:
|
| 609 |
update_heartbeat()
|
|
@@ -624,6 +603,7 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 624 |
words = state["words"]
|
| 625 |
user_data_md = state["user_data_md"]
|
| 626 |
user_lit_context = state["user_lit_context"]
|
|
|
|
| 627 |
|
| 628 |
agent.load_state(state["agent_state"])
|
| 629 |
adaptive_structure = state["adaptive_structure"]
|
|
@@ -647,6 +627,7 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 647 |
pb = progress_bar.progress(1, text=tr(lang_code, "pipeline_start"))
|
| 648 |
user_lit_context = ""
|
| 649 |
user_data_md = ""
|
|
|
|
| 650 |
|
| 651 |
if st.session_state.run_mode == "amj":
|
| 652 |
if uploaded_papers_amj:
|
|
@@ -669,7 +650,12 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 669 |
|
| 670 |
with st.spinner(tr(lang_code, "extract_quant")):
|
| 671 |
update_heartbeat()
|
| 672 |
-
_, user_data_md, msg =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
st.info(msg)
|
| 674 |
|
| 675 |
elif st.session_state.run_mode == "asq":
|
|
@@ -693,14 +679,21 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 693 |
|
| 694 |
update_heartbeat()
|
| 695 |
raw_interviews = extract_text_from_files([uploaded_interviews]) if uploaded_interviews else ""
|
| 696 |
-
user_data_md, msg =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 697 |
st.info(tr(lang_code, "qualitative_ready"))
|
| 698 |
|
| 699 |
state = {
|
| 700 |
"topic": topic, "keywords": keywords, "target_lang": target_lang, "words": words,
|
| 701 |
"user_data_md": user_data_md, "user_lit_context": user_lit_context,
|
| 702 |
"completed_sections": [], "full_draft_en": "", "full_draft_translated": "",
|
| 703 |
-
"context_tail": "", "run_mode": st.session_state.run_mode, "interface_language": lang_code
|
|
|
|
| 704 |
}
|
| 705 |
|
| 706 |
pb.progress(5, text=tr(lang_code, "config_route"))
|
|
@@ -755,7 +748,18 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 755 |
pb.progress(14, text=tr(lang_code, "anchor_progress"))
|
| 756 |
with st.status(tr(lang_code, "anchor_status"), expanded=True) as status:
|
| 757 |
ph = st.empty()
|
| 758 |
-
anchor_response = ph.write_stream(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 759 |
status.update(label=tr(lang_code, "anchor_done"), state="complete", expanded=False)
|
| 760 |
|
| 761 |
# ==========================================================
|
|
@@ -767,6 +771,8 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 767 |
conclusion_match = re.search(r'-+\s*CONCLUSION\s*-+\s*(.*?)$', anchor_response_clean, re.DOTALL | re.IGNORECASE)
|
| 768 |
abstract_anchor = abstract_match.group(1).strip() if abstract_match else anchor_response_clean.strip()
|
| 769 |
conclusion_anchor = conclusion_match.group(1).strip() if conclusion_match else ""
|
|
|
|
|
|
|
| 770 |
# ==========================================================
|
| 771 |
|
| 772 |
pb.progress(15, text=tr(lang_code, "outline_progress"))
|
|
@@ -776,7 +782,14 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 776 |
structure = plan.get("structure", [])
|
| 777 |
|
| 778 |
en_abstract_clean = clean_markdown_headings(re.sub(r'(?i)^abstract[:\s]*', '', str(abstract_anchor)).strip())
|
| 779 |
-
trans_abstract_clean = clean_markdown_headings(re.sub(r'(?i)^(摘要|abstract|аннотация|түйіндеме)[:\s]*', '', agent.call_deepseek(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
|
| 781 |
loc_abs = {"Simplified Chinese (简体中文)": "摘要", "Russian (Русский)": "Аннотация", "Kazakh (Қазақша)": "Түйіндеме"}.get(target_lang, "Abstract")
|
| 782 |
loc_kw = {"Simplified Chinese (简体中文)": "关键词", "Russian (Русский)": "Ключевые слова", "Kazakh (Қазақша)": "Кілт сөздер"}.get(target_lang, "Keywords")
|
|
@@ -992,11 +1005,18 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 992 |
update_heartbeat()
|
| 993 |
st.write(tr(lang_code, "api_requested"))
|
| 994 |
|
| 995 |
-
target_model = "
|
| 996 |
st.write(tr(lang_code, "model_writing", model=target_model))
|
| 997 |
|
| 998 |
en_draft_clean = re.sub(r'<think>.*?</think>', '', en_ph.write_stream(
|
| 999 |
-
stream_with_heartbeat(agent.stream_deepseek(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1000 |
), flags=re.DOTALL).strip()
|
| 1001 |
|
| 1002 |
status.update(label=tr(lang_code, "draft_done", name=display_name), state="complete", expanded=False)
|
|
@@ -1101,7 +1121,14 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 1101 |
if total_words_en > upper_limit:
|
| 1102 |
conclusion_match = re.search(r'(#+\s*Conclusion.*?)(?=\n#+ |\Z)', full_draft_en, re.DOTALL | re.IGNORECASE)
|
| 1103 |
if conclusion_match:
|
| 1104 |
-
compressed_en = agent.call_deepseek(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1105 |
if compressed_en:
|
| 1106 |
compressed_en = agent.resolve_citations(compressed_en, quarantine=True)
|
| 1107 |
full_draft_en = full_draft_en.replace(conclusion_match.group(1), compressed_en.strip())
|
|
@@ -1114,7 +1141,14 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 1114 |
else:
|
| 1115 |
full_draft_translated += "\n\n" + compressed_trans.strip()
|
| 1116 |
elif total_words_en < lower_limit:
|
| 1117 |
-
expanded_en = agent.call_deepseek(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1118 |
if expanded_en:
|
| 1119 |
expanded_en = agent.resolve_citations(expanded_en, quarantine=True)
|
| 1120 |
full_draft_en += "\n\n" + expanded_en.strip()
|
|
@@ -1136,6 +1170,13 @@ if st.session_state.start_pipeline or st.session_state.resume_pipeline:
|
|
| 1136 |
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
|
| 1137 |
zf.writestr(f"Research_EN_{bj_time}.docx", text_to_docx(full_draft_en).getvalue())
|
| 1138 |
zf.writestr(f"Research_{output_suffix}_{bj_time}.docx", text_to_docx(full_draft_translated).getvalue())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1139 |
zip_buffer.seek(0)
|
| 1140 |
st.session_state.zip_buffer = zip_buffer.getvalue()
|
| 1141 |
st.session_state.zip_filename = f"Draft_{bj_time}.zip"
|
|
|
|
| 3 |
from core.meta_extractor import extract_golden_metadata
|
| 4 |
from core.rag_engine import initialize_rag
|
| 5 |
from core.qualitative_engine import get_qualitative_engine
|
| 6 |
+
from core.quantitative_engine import get_quantitative_engine
|
| 7 |
import streamlit as st
|
| 8 |
|
| 9 |
st.set_page_config(
|
|
|
|
| 25 |
import math
|
| 26 |
import json
|
| 27 |
from datetime import datetime, timezone, timedelta
|
|
|
|
|
|
|
| 28 |
import threading
|
| 29 |
from concurrent.futures import ThreadPoolExecutor
|
| 30 |
from streamlit.runtime.scriptrunner import add_script_run_ctx, get_script_run_ctx
|
| 31 |
|
| 32 |
from config import APP_CONFIG, AMJ_PROMPTS, ASQ_PROMPTS, GENERIC_PROMPTS
|
| 33 |
+
from utils.file_parser import extract_text_from_files
|
| 34 |
from utils.docx_builder import text_to_docx
|
| 35 |
from core.pipeline_agent import SSciPipelineAgent
|
| 36 |
from managed_auth import require_alipay_login, render_logged_in_account
|
|
|
|
| 123 |
|
| 124 |
global_lock = get_global_lock()
|
| 125 |
|
| 126 |
+
def build_generation_disclosure(run_mode, data_metadata):
|
| 127 |
+
metadata = data_metadata or {}
|
| 128 |
+
source_type = metadata.get("source_type", "unknown")
|
| 129 |
+
generated = source_type == "system_generated"
|
| 130 |
+
mode_label = "quantitative" if run_mode == "amj" else "qualitative"
|
| 131 |
+
|
| 132 |
+
lines = [
|
| 133 |
+
"SmartPaper Generation Disclosure / SmartPaper 生成说明",
|
| 134 |
+
"",
|
| 135 |
+
f"Research mode / 研究模式: {mode_label}",
|
| 136 |
+
f"Evidence source / 证据来源: {source_type}",
|
| 137 |
+
"",
|
| 138 |
+
]
|
| 139 |
+
if generated:
|
| 140 |
+
lines.extend(
|
| 141 |
+
[
|
| 142 |
+
"The statistical results or qualitative materials used by "
|
| 143 |
+
"this draft were generated by the system because no "
|
| 144 |
+
"complete user-supplied evidence was available.",
|
| 145 |
+
"本初稿使用的统计结果或定性材料由系统生成,原因是用户未提供完整的相应证据材料。",
|
| 146 |
+
"",
|
| 147 |
+
"These materials support drafting and methodological "
|
| 148 |
+
"demonstration only. They must not be represented as "
|
| 149 |
+
"observed, collected, or independently verified real data.",
|
| 150 |
+
"这些材料仅用于论文起草和方法展示,不得被表述为已经观察、收集或独立核验的真实数据。",
|
| 151 |
+
"",
|
| 152 |
+
"The generated evidence is bounded by the configured "
|
| 153 |
+
"parameters and prompts. Users must replace or validate it "
|
| 154 |
+
"before research submission, publication, policy use, or "
|
| 155 |
+
"other consequential use.",
|
| 156 |
+
"生成证据受系统参数和提示词约束。用于投稿、发表、政策或其他重要用途前,用户必须替换或核验。",
|
| 157 |
+
]
|
| 158 |
+
)
|
| 159 |
+
else:
|
| 160 |
+
lines.extend(
|
| 161 |
+
[
|
| 162 |
+
"The draft used evidence material supplied by the user. "
|
| 163 |
+
"SmartPaper did not independently verify its accuracy, "
|
| 164 |
+
"completeness, consent status, or provenance.",
|
| 165 |
+
"本初稿使用了用户提供的证据材料。SmartPaper未独立核验其准确性、完整性、授权状态或来源。",
|
| 166 |
+
]
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
method = metadata.get("method")
|
| 170 |
+
if method:
|
| 171 |
+
lines.extend(["", f"Generation method / 生成方法: {method}"])
|
| 172 |
+
facts = metadata.get("study_facts") or {}
|
| 173 |
+
if facts:
|
| 174 |
+
lines.extend(
|
| 175 |
+
[
|
| 176 |
+
"",
|
| 177 |
+
"Frozen qualitative study facts / 已冻结的定性研究事实:",
|
| 178 |
+
json.dumps(facts, ensure_ascii=False, indent=2),
|
| 179 |
+
]
|
| 180 |
+
)
|
| 181 |
+
return "\n".join(lines).strip() + "\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
def _translation_system_prompt(target_lang):
|
| 184 |
common = """Translate the supplied academic manuscript faithfully. Preserve every citation tag, number, percentage, coefficient, p-value, confidence interval, DOI, table structure, heading level, hypothesis label, variable abbreviation, and conclusion direction. Do not add, remove, strengthen, or weaken empirical claims. Use concise, accurate, and rigorous academic language. Do not use metaphors, analogies, rhetorical questions, promotional language, formulaic transitions, or mechanical repetition. Output only the translated manuscript."""
|
|
|
|
| 192 |
def async_translate_task(agent, en_text, placeholder, target_lang, quarantine_flag, interface_lang):
|
| 193 |
try:
|
| 194 |
sys_p = _translation_system_prompt(target_lang)
|
| 195 |
+
trans_text = agent.call_deepseek(
|
| 196 |
+
sys_p,
|
| 197 |
+
f"TEXT:\n{en_text}",
|
| 198 |
+
max_tokens=6000,
|
| 199 |
+
model_role="translation",
|
| 200 |
+
thinking=False,
|
| 201 |
+
allow_fallback=False,
|
| 202 |
+
)
|
| 203 |
trans_resolved = agent.resolve_citations(trans_text, quarantine=quarantine_flag)
|
| 204 |
placeholder.markdown(trans_resolved)
|
| 205 |
return trans_resolved
|
|
|
|
| 582 |
SAFE_CHUNK_WORDS = agent.chunk_words_cyrillic if target_lang in ["Russian (Русский)", "Kazakh (Қазақша)"] else agent.chunk_words_default
|
| 583 |
|
| 584 |
qual_engine = get_qualitative_engine(agent)
|
| 585 |
+
quant_engine = get_quantitative_engine()
|
| 586 |
|
| 587 |
try:
|
| 588 |
update_heartbeat()
|
|
|
|
| 603 |
words = state["words"]
|
| 604 |
user_data_md = state["user_data_md"]
|
| 605 |
user_lit_context = state["user_lit_context"]
|
| 606 |
+
data_metadata = state.get("data_metadata", {})
|
| 607 |
|
| 608 |
agent.load_state(state["agent_state"])
|
| 609 |
adaptive_structure = state["adaptive_structure"]
|
|
|
|
| 627 |
pb = progress_bar.progress(1, text=tr(lang_code, "pipeline_start"))
|
| 628 |
user_lit_context = ""
|
| 629 |
user_data_md = ""
|
| 630 |
+
data_metadata = {}
|
| 631 |
|
| 632 |
if st.session_state.run_mode == "amj":
|
| 633 |
if uploaded_papers_amj:
|
|
|
|
| 650 |
|
| 651 |
with st.spinner(tr(lang_code, "extract_quant")):
|
| 652 |
update_heartbeat()
|
| 653 |
+
_, user_data_md, msg, data_metadata = (
|
| 654 |
+
quant_engine.resolve_empirical_data(
|
| 655 |
+
uploaded_data_amj,
|
| 656 |
+
lang_code,
|
| 657 |
+
)
|
| 658 |
+
)
|
| 659 |
st.info(msg)
|
| 660 |
|
| 661 |
elif st.session_state.run_mode == "asq":
|
|
|
|
| 679 |
|
| 680 |
update_heartbeat()
|
| 681 |
raw_interviews = extract_text_from_files([uploaded_interviews]) if uploaded_interviews else ""
|
| 682 |
+
user_data_md, msg, data_metadata = (
|
| 683 |
+
qual_engine.resolve_qualitative_data(
|
| 684 |
+
topic,
|
| 685 |
+
keywords,
|
| 686 |
+
raw_interviews,
|
| 687 |
+
)
|
| 688 |
+
)
|
| 689 |
st.info(tr(lang_code, "qualitative_ready"))
|
| 690 |
|
| 691 |
state = {
|
| 692 |
"topic": topic, "keywords": keywords, "target_lang": target_lang, "words": words,
|
| 693 |
"user_data_md": user_data_md, "user_lit_context": user_lit_context,
|
| 694 |
"completed_sections": [], "full_draft_en": "", "full_draft_translated": "",
|
| 695 |
+
"context_tail": "", "run_mode": st.session_state.run_mode, "interface_language": lang_code,
|
| 696 |
+
"data_metadata": data_metadata
|
| 697 |
}
|
| 698 |
|
| 699 |
pb.progress(5, text=tr(lang_code, "config_route"))
|
|
|
|
| 748 |
pb.progress(14, text=tr(lang_code, "anchor_progress"))
|
| 749 |
with st.status(tr(lang_code, "anchor_status"), expanded=True) as status:
|
| 750 |
ph = st.empty()
|
| 751 |
+
anchor_response = ph.write_stream(
|
| 752 |
+
stream_with_heartbeat(
|
| 753 |
+
agent.stream_generate_anchors(
|
| 754 |
+
topic,
|
| 755 |
+
keywords,
|
| 756 |
+
synthesis_result,
|
| 757 |
+
refs_text,
|
| 758 |
+
data_evidence=user_data_md,
|
| 759 |
+
mode=st.session_state.run_mode,
|
| 760 |
+
)
|
| 761 |
+
)
|
| 762 |
+
)
|
| 763 |
status.update(label=tr(lang_code, "anchor_done"), state="complete", expanded=False)
|
| 764 |
|
| 765 |
# ==========================================================
|
|
|
|
| 771 |
conclusion_match = re.search(r'-+\s*CONCLUSION\s*-+\s*(.*?)$', anchor_response_clean, re.DOTALL | re.IGNORECASE)
|
| 772 |
abstract_anchor = abstract_match.group(1).strip() if abstract_match else anchor_response_clean.strip()
|
| 773 |
conclusion_anchor = conclusion_match.group(1).strip() if conclusion_match else ""
|
| 774 |
+
agent.global_memory["abstract_anchor"] = abstract_anchor
|
| 775 |
+
agent.global_memory["conclusion_anchor"] = conclusion_anchor
|
| 776 |
# ==========================================================
|
| 777 |
|
| 778 |
pb.progress(15, text=tr(lang_code, "outline_progress"))
|
|
|
|
| 782 |
structure = plan.get("structure", [])
|
| 783 |
|
| 784 |
en_abstract_clean = clean_markdown_headings(re.sub(r'(?i)^abstract[:\s]*', '', str(abstract_anchor)).strip())
|
| 785 |
+
trans_abstract_clean = clean_markdown_headings(re.sub(r'(?i)^(摘要|abstract|аннотация|түйіндеме)[:\s]*', '', agent.call_deepseek(
|
| 786 |
+
_translation_system_prompt(target_lang),
|
| 787 |
+
abstract_anchor,
|
| 788 |
+
max_tokens=3000,
|
| 789 |
+
model_role="translation",
|
| 790 |
+
thinking=False,
|
| 791 |
+
allow_fallback=False,
|
| 792 |
+
)).strip())
|
| 793 |
|
| 794 |
loc_abs = {"Simplified Chinese (简体中文)": "摘要", "Russian (Русский)": "Аннотация", "Kazakh (Қазақша)": "Түйіндеме"}.get(target_lang, "Abstract")
|
| 795 |
loc_kw = {"Simplified Chinese (简体中文)": "关键词", "Russian (Русский)": "Ключевые слова", "Kazakh (Қазақша)": "Кілт сөздер"}.get(target_lang, "Keywords")
|
|
|
|
| 1005 |
update_heartbeat()
|
| 1006 |
st.write(tr(lang_code, "api_requested"))
|
| 1007 |
|
| 1008 |
+
target_model = agent.model_for("writing")
|
| 1009 |
st.write(tr(lang_code, "model_writing", model=target_model))
|
| 1010 |
|
| 1011 |
en_draft_clean = re.sub(r'<think>.*?</think>', '', en_ph.write_stream(
|
| 1012 |
+
stream_with_heartbeat(agent.stream_deepseek(
|
| 1013 |
+
sys_prompt_m3,
|
| 1014 |
+
base_user,
|
| 1015 |
+
max_tokens=6000,
|
| 1016 |
+
model_role="writing",
|
| 1017 |
+
thinking=False,
|
| 1018 |
+
allow_fallback=False,
|
| 1019 |
+
))
|
| 1020 |
), flags=re.DOTALL).strip()
|
| 1021 |
|
| 1022 |
status.update(label=tr(lang_code, "draft_done", name=display_name), state="complete", expanded=False)
|
|
|
|
| 1121 |
if total_words_en > upper_limit:
|
| 1122 |
conclusion_match = re.search(r'(#+\s*Conclusion.*?)(?=\n#+ |\Z)', full_draft_en, re.DOTALL | re.IGNORECASE)
|
| 1123 |
if conclusion_match:
|
| 1124 |
+
compressed_en = agent.call_deepseek(
|
| 1125 |
+
"Condense the following conclusion by 15-20% preserving citations.",
|
| 1126 |
+
conclusion_match.group(1),
|
| 1127 |
+
max_tokens=1500,
|
| 1128 |
+
model_role="writing",
|
| 1129 |
+
thinking=False,
|
| 1130 |
+
allow_fallback=False,
|
| 1131 |
+
)
|
| 1132 |
if compressed_en:
|
| 1133 |
compressed_en = agent.resolve_citations(compressed_en, quarantine=True)
|
| 1134 |
full_draft_en = full_draft_en.replace(conclusion_match.group(1), compressed_en.strip())
|
|
|
|
| 1141 |
else:
|
| 1142 |
full_draft_translated += "\n\n" + compressed_trans.strip()
|
| 1143 |
elif total_words_en < lower_limit:
|
| 1144 |
+
expanded_en = agent.call_deepseek(
|
| 1145 |
+
"Continue conclusion by adding 150 words (future research).",
|
| 1146 |
+
full_draft_en[-2000:],
|
| 1147 |
+
max_tokens=800,
|
| 1148 |
+
model_role="writing",
|
| 1149 |
+
thinking=False,
|
| 1150 |
+
allow_fallback=False,
|
| 1151 |
+
)
|
| 1152 |
if expanded_en:
|
| 1153 |
expanded_en = agent.resolve_citations(expanded_en, quarantine=True)
|
| 1154 |
full_draft_en += "\n\n" + expanded_en.strip()
|
|
|
|
| 1170 |
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
|
| 1171 |
zf.writestr(f"Research_EN_{bj_time}.docx", text_to_docx(full_draft_en).getvalue())
|
| 1172 |
zf.writestr(f"Research_{output_suffix}_{bj_time}.docx", text_to_docx(full_draft_translated).getvalue())
|
| 1173 |
+
zf.writestr(
|
| 1174 |
+
"GENERATION_DISCLOSURE.txt",
|
| 1175 |
+
build_generation_disclosure(
|
| 1176 |
+
st.session_state.run_mode,
|
| 1177 |
+
data_metadata,
|
| 1178 |
+
),
|
| 1179 |
+
)
|
| 1180 |
zip_buffer.seek(0)
|
| 1181 |
st.session_state.zip_buffer = zip_buffer.getvalue()
|
| 1182 |
st.session_state.zip_filename = f"Draft_{bj_time}.zip"
|
config.py
CHANGED
|
@@ -23,32 +23,65 @@ def load_config():
|
|
| 23 |
|
| 24 |
# --- API 节点配置 ---
|
| 25 |
"deepseek_api_url": os.environ.get(
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
"
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
"openalex_api_url": os.environ.get("OPENALEX_API_URL", "https://api.openalex.org/works"),
|
| 53 |
"s2_api_url": os.environ.get("S2_API_URL", "https://api.semanticscholar.org/graph/v1"),
|
| 54 |
|
|
|
|
| 23 |
|
| 24 |
# --- API 节点配置 ---
|
| 25 |
"deepseek_api_url": os.environ.get(
|
| 26 |
+
"DEEPSEEK_API_URL",
|
| 27 |
+
"https://api.deepseek.com/chat/completions",
|
| 28 |
+
),
|
| 29 |
+
# Model names are centralized here and can be upgraded in HF Variables.
|
| 30 |
+
# Application and engine code select a task role, never a concrete model.
|
| 31 |
+
"deepseek_models": {
|
| 32 |
+
"reasoning": os.environ.get(
|
| 33 |
+
"DEEPSEEK_MODEL_REASONING",
|
| 34 |
+
"deepseek-v4-pro",
|
| 35 |
+
),
|
| 36 |
+
"writing": os.environ.get(
|
| 37 |
+
"DEEPSEEK_MODEL_WRITING",
|
| 38 |
+
"deepseek-v4-pro",
|
| 39 |
+
),
|
| 40 |
+
"translation": os.environ.get(
|
| 41 |
+
"DEEPSEEK_MODEL_TRANSLATION",
|
| 42 |
+
"deepseek-v4-pro",
|
| 43 |
+
),
|
| 44 |
+
"fast": os.environ.get(
|
| 45 |
+
"DEEPSEEK_MODEL_FAST",
|
| 46 |
+
"deepseek-v4-flash",
|
| 47 |
+
),
|
| 48 |
+
"fallback": os.environ.get(
|
| 49 |
+
"DEEPSEEK_MODEL_FALLBACK",
|
| 50 |
+
"deepseek-v4-flash",
|
| 51 |
+
),
|
| 52 |
+
},
|
| 53 |
+
"deepseek_task_settings": {
|
| 54 |
+
"reasoning": {
|
| 55 |
+
"thinking": True,
|
| 56 |
+
"reasoning_effort": os.environ.get(
|
| 57 |
+
"DEEPSEEK_REASONING_EFFORT",
|
| 58 |
+
"high",
|
| 59 |
+
),
|
| 60 |
+
"allow_fallback": False,
|
| 61 |
+
},
|
| 62 |
+
"writing": {
|
| 63 |
+
"thinking": False,
|
| 64 |
+
"reasoning_effort": "high",
|
| 65 |
+
"allow_fallback": False,
|
| 66 |
+
},
|
| 67 |
+
"translation": {
|
| 68 |
+
"thinking": False,
|
| 69 |
+
"reasoning_effort": "high",
|
| 70 |
+
"allow_fallback": False,
|
| 71 |
+
},
|
| 72 |
+
"fast": {
|
| 73 |
+
"thinking": False,
|
| 74 |
+
"reasoning_effort": "low",
|
| 75 |
+
"allow_fallback": True,
|
| 76 |
+
},
|
| 77 |
+
},
|
| 78 |
+
"deepseek_max_retries": int(
|
| 79 |
+
os.environ.get("DEEPSEEK_MAX_RETRIES", "3")
|
| 80 |
+
),
|
| 81 |
+
"deepseek_show_reasoning": os.environ.get(
|
| 82 |
+
"DEEPSEEK_SHOW_REASONING",
|
| 83 |
+
"false",
|
| 84 |
+
).strip().lower() in {"1", "true", "yes", "on"},
|
| 85 |
"openalex_api_url": os.environ.get("OPENALEX_API_URL", "https://api.openalex.org/works"),
|
| 86 |
"s2_api_url": os.environ.get("S2_API_URL", "https://api.semanticscholar.org/graph/v1"),
|
| 87 |
|