Skip to content

Commit 9513e87

Browse files
committed
Add report language prompts and card actions
1 parent 2cef330 commit 9513e87

7 files changed

Lines changed: 305 additions & 3 deletions

File tree

agents/master-coordinator/scripts/llm_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,7 @@ def synthesize_reading_report_with_llm(
17121712
if language == "en":
17131713
system_prompt = (
17141714
"You are a maximalist research-paper deep-reading assistant and information source. Your goal is to give the user the richest possible raw material for their own judgment. "
1715+
"The PaperFlow GUI currently selected English as the response language; this is a hard requirement. Use English for every generated report field, regardless of the paper's original language or mixed-language evidence. "
17151716
"Use English for all output fields. Completeness is more important than compression: default to exhaustive coverage of every relevant angle, sub-topic, edge case, adjacent consideration, and cross-domain connection you can identify from the paper evidence. "
17161717
"When in doubt between including and omitting, include. The user will filter; your job is to ensure nothing important is missing. Redundancy is acceptable; gaps are not. "
17171718
"Structure each long field for scanning with clear paragraphs, numbered lists, compact tables when useful, and explicit subtopics inside the JSON string. Long answers are fine; unstructured long answers are not. "
@@ -1743,6 +1744,7 @@ def synthesize_reading_report_with_llm(
17431744
else:
17441745
system_prompt = (
17451746
"你是 maximalist 风格的科研论文精读助手和信息源,目标是给用户提供尽可能丰富、可判断、可复核的中文精读原材料,让用户自己判断取舍。"
1747+
"PaperFlow GUI 页面当前选择的回答语言是中文;这是硬性要求。无论论文原文、PDF 证据或元数据是什么语言,所有生成的精读报告字段都必须用中文书写。"
17461748
"完整性无条件优先于压缩:默认穷尽覆盖论文动机、相关研究、技术路线、实验设计、关键发现、局限、边界条件、反例、失败模式、可探索方向、相邻问题和跨领域联系;"
17471749
"当你在“写进去”和“省略”之间犹豫时,选择写进去。用户会自行筛选;你的职责是尽量不漏信息。适度重复可以接受,信息缺口不可以接受。"
17481750
"结构必须高度可扫读,适合飞书文档阅读;每个长字段应使用清晰小标题、编号列表、短表格式描述或分点组织。长回答没问题,但不能是无结构长段落。"

deployments/desktop/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ def _api_report_content(query_params: Dict[str, Any], _body: Dict[str, Any]) ->
219219
return agents.get_report_content(str(query_params.get("report_id") or "").strip())
220220

221221

222+
def _api_report_delete(_query_params: Dict[str, Any], body: Dict[str, Any]) -> Dict[str, Any]:
223+
return agents.delete_report(str(body.get("report_id") or "").strip())
224+
225+
222226
def _api_read_status(query_params: Dict[str, Any], _body: Dict[str, Any]) -> Dict[str, Any]:
223227
return agents.get_reading_reports_task(str(query_params.get("task_id") or "").strip())
224228

@@ -467,6 +471,7 @@ def _api_must_read_update(_query_params: Dict[str, Any], body: Dict[str, Any]) -
467471
"/api/read/arxiv": _api_read_arxiv,
468472
"/api/read/pdf": _api_read_pdf,
469473
"/api/submit": _api_submit,
474+
"/api/reports/delete": _api_report_delete,
470475
"/api/wiki/ask": _api_wiki_ask,
471476
"/api/wiki/ask/stream": _api_wiki_ask,
472477
"/api/wiki/refresh": _api_wiki_refresh,

deployments/desktop/shared/agents.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ def _report_record(path: Path) -> Dict[str, Any]:
681681
body = _strip_duplicate_report_heading(body, title)
682682
return {
683683
"report_id": _report_id(path),
684+
"_path": str(path.resolve()),
684685
"title": title,
685686
"user_id": str(metadata.get("user_id") or "").strip(),
686687
"paper_id": metadata.get("paper_id"),
@@ -2657,6 +2658,12 @@ def _write_reading_placeholder(
26572658
report_path.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
26582659

26592660

2661+
def _reading_language_notice(response_language: str) -> str:
2662+
if _normalize_response_language(response_language) == "en":
2663+
return "The GUI language is set to English; this reading report will be written in English."
2664+
return "GUI 页面当前选择语言为中文;本次精读报告将按中文生成。"
2665+
2666+
26602667
def _reading_task_payload(task: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
26612668
if not task:
26622669
return None
@@ -2696,6 +2703,7 @@ def _finish_reading_task(task_id: str) -> None:
26962703
status="running",
26972704
steps=[
26982705
"已创建精读报告占位文件。",
2706+
_reading_language_notice(response_language),
26992707
"正在解析 PDF;如已配置 MinerU,会优先使用 MinerU。",
27002708
"正在调用 LLM 生成 Q1-Q7 精读内容。",
27012709
],
@@ -2723,7 +2731,7 @@ def _finish_reading_task(task_id: str) -> None:
27232731
paper=paper,
27242732
report_path=report_path,
27252733
status="failed",
2726-
steps=["已创建精读报告占位文件。", "后台精读任务执行失败。"],
2734+
steps=["已创建精读报告占位文件。", _reading_language_notice(response_language), "后台精读任务执行失败。"],
27272735
error=error,
27282736
response_language=response_language,
27292737
)
@@ -2766,7 +2774,7 @@ def start_reading_reports_task(
27662774
paper=normalized_paper,
27672775
report_path=report_path,
27682776
status="queued",
2769-
steps=["已加入后台精读队列。", "刷新本页面可查看最新进度。"],
2777+
steps=["已加入后台精读队列。", _reading_language_notice(language), "刷新本页面可查看最新进度。"],
27702778
response_language=language,
27712779
)
27722780
docs.append(
@@ -2952,6 +2960,29 @@ def get_report_content(report_id: str) -> Dict[str, Any]:
29522960
raise ValueError(f"Report not found: {normalized_id}")
29532961

29542962

2963+
def delete_report(report_id: str) -> Dict[str, Any]:
2964+
normalized_id = str(report_id or "").strip()
2965+
if not normalized_id:
2966+
raise ValueError("report_id is required")
2967+
allowed_roots = [path.resolve() for path in _reading_report_dirs()]
2968+
for report in _all_report_records():
2969+
if report["report_id"] != normalized_id:
2970+
continue
2971+
path = Path(str(report.get("_path") or "")).resolve()
2972+
if path.suffix.lower() != ".md" or not path.is_file():
2973+
raise ValueError(f"Report file not found: {normalized_id}")
2974+
if not any(path == root or path.is_relative_to(root) for root in allowed_roots):
2975+
raise ValueError("Report path is outside configured report directories")
2976+
path.unlink()
2977+
return {
2978+
"deleted": True,
2979+
"report_id": normalized_id,
2980+
"title": report.get("title") or "",
2981+
"report_path": _display_path(path),
2982+
}
2983+
raise ValueError(f"Report not found: {normalized_id}")
2984+
2985+
29552986
def _report_doc_for_wiki_backfill(report: Dict[str, Any]) -> Dict[str, Any]:
29562987
metadata = dict(report.get("metadata") or {})
29572988
paper = {

deployments/desktop/static/desktop.css

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,45 @@ body.chat-view #chat {
11261126
line-height: 1.34;
11271127
}
11281128

1129+
.report-context-menu {
1130+
position: fixed;
1131+
z-index: 1200;
1132+
min-width: 132px;
1133+
padding: 4px;
1134+
border: 1px solid #d9e3f1;
1135+
border-radius: 6px;
1136+
background: #fff;
1137+
box-shadow: 0 12px 28px rgba(31, 48, 79, 0.16);
1138+
}
1139+
1140+
.report-context-menu.hidden {
1141+
display: none;
1142+
}
1143+
1144+
.report-context-menu button {
1145+
display: block;
1146+
width: 100%;
1147+
padding: 7px 9px;
1148+
border: 0;
1149+
border-radius: 4px;
1150+
background: transparent;
1151+
color: var(--text);
1152+
text-align: left;
1153+
font-size: 12px;
1154+
}
1155+
1156+
.report-context-menu button:hover {
1157+
background: #f2f6fb;
1158+
}
1159+
1160+
.report-context-menu button.danger {
1161+
color: #b42318;
1162+
}
1163+
1164+
.report-context-menu button.danger:hover {
1165+
background: #fff1f0;
1166+
}
1167+
11291168
.report-reader {
11301169
padding: 16px;
11311170
min-height: 640px;

deployments/desktop/static/desktop.js

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
currentReport: null,
2727
reportAnnotationRange: null,
2828
highlightedReports: new Set(),
29+
reportContextMenu: null,
2930
readingPollToken: 0,
3031
wikiNodes: [],
3132
wikiGraph: null,
@@ -1823,6 +1824,9 @@
18231824
if (route === "/api/reports/content") {
18241825
return { ok: true, report: demoReports.find((item) => item.report_id === url.searchParams.get("report_id")) || demoReports[0] };
18251826
}
1827+
if (route === "/api/reports/delete") {
1828+
return { ok: true, deleted: true, report_id: body.report_id || "" };
1829+
}
18261830
if (route === "/api/wiki/stats") {
18271831
return { ok: true, nodes: 112, edges: 248, citations: 436, wiki_dir: "data/wiki/user_demo" };
18281832
}
@@ -2718,6 +2722,88 @@
27182722
renderReportList(state.reports, state.currentReport?.report_id || reportId);
27192723
}
27202724

2725+
function reportContextLabels(reportId) {
2726+
const isEn = responseLanguage() === "en";
2727+
return {
2728+
highlight: state.highlightedReports.has(reportId)
2729+
? (isEn ? "Remove highlight" : "取消高亮")
2730+
: (isEn ? "Highlight" : "高亮"),
2731+
delete: isEn ? "Delete report" : "删除报告"
2732+
};
2733+
}
2734+
2735+
function ensureReportContextMenu() {
2736+
if (state.reportContextMenu) return state.reportContextMenu;
2737+
const menu = document.createElement("div");
2738+
menu.id = "reportContextMenu";
2739+
menu.className = "report-context-menu hidden";
2740+
menu.setAttribute("role", "menu");
2741+
menu.innerHTML = `
2742+
<button type="button" data-report-menu-action="highlight" role="menuitem"></button>
2743+
<button type="button" data-report-menu-action="delete" role="menuitem" class="danger"></button>
2744+
`;
2745+
menu.addEventListener("click", (event) => {
2746+
const button = event.target.closest("[data-report-menu-action]");
2747+
if (!button) return;
2748+
const reportId = menu.dataset.reportId || "";
2749+
const action = button.dataset.reportMenuAction;
2750+
hideReportContextMenu();
2751+
if (action === "highlight") {
2752+
toggleReportCardHighlight(reportId);
2753+
} else if (action === "delete") {
2754+
runAction(() => deleteReportCard(reportId), "删除报告");
2755+
}
2756+
});
2757+
document.body.appendChild(menu);
2758+
state.reportContextMenu = menu;
2759+
return menu;
2760+
}
2761+
2762+
function hideReportContextMenu() {
2763+
if (!state.reportContextMenu) return;
2764+
state.reportContextMenu.classList.add("hidden");
2765+
state.reportContextMenu.dataset.reportId = "";
2766+
}
2767+
2768+
function showReportContextMenu(reportId, x, y) {
2769+
if (!reportId) return;
2770+
const menu = ensureReportContextMenu();
2771+
const labels = reportContextLabels(reportId);
2772+
menu.dataset.reportId = reportId;
2773+
menu.querySelector('[data-report-menu-action="highlight"]').textContent = labels.highlight;
2774+
menu.querySelector('[data-report-menu-action="delete"]').textContent = labels.delete;
2775+
menu.classList.remove("hidden");
2776+
const rect = menu.getBoundingClientRect();
2777+
const left = Math.min(Math.max(8, x), window.innerWidth - rect.width - 8);
2778+
const top = Math.min(Math.max(8, y), window.innerHeight - rect.height - 8);
2779+
menu.style.left = `${left}px`;
2780+
menu.style.top = `${top}px`;
2781+
}
2782+
2783+
async function deleteReportCard(reportId) {
2784+
if (!reportId) return;
2785+
const report = state.reports.find((item) => item.report_id === reportId) || state.currentReport || {};
2786+
const title = report.title || ui().reports.defaultTitle;
2787+
const confirmText = responseLanguage() === "en"
2788+
? `Delete this local reading report?\n\n${title}`
2789+
: `删除这个本地精读报告?\n\n${title}`;
2790+
if (!window.confirm(confirmText)) return;
2791+
await api("/api/reports/delete", {
2792+
method: "POST",
2793+
body: JSON.stringify({ report_id: reportId })
2794+
});
2795+
state.highlightedReports.delete(reportId);
2796+
saveReportCardHighlights();
2797+
try {
2798+
window.localStorage.removeItem(reportAnnotationKey(reportId));
2799+
} catch (error) {
2800+
console.warn("Unable to clear deleted report annotation", error);
2801+
}
2802+
if (state.currentReport?.report_id === reportId) clearReportViewer();
2803+
showFeedbackToast("success", responseLanguage() === "en" ? "Report deleted" : "报告已删除", title);
2804+
await refreshReports({ keepSelection: false });
2805+
}
2806+
27212807
function loadReportAnnotation(reportId) {
27222808
if (!reportId) return "";
27232809
try {
@@ -2836,6 +2922,17 @@
28362922
`).join("");
28372923
}
28382924

2925+
function clearReportViewer() {
2926+
state.currentReport = null;
2927+
$("reportViewerTitle").textContent = ui().reports.defaultTitle;
2928+
$("reportViewerMeta").textContent = "";
2929+
$("reportViewerBody").className = "markdown-body";
2930+
$("reportViewerBody").innerHTML = "";
2931+
$("clearReportAnnotationsBtn").disabled = true;
2932+
hideReportAnnotationToolbar();
2933+
setReportLinks({});
2934+
}
2935+
28392936
async function refreshReports(options = {}) {
28402937
const scopedUser = $("reportsCurrentOnly").checked ? currentUser() : "";
28412938
const query = $("reportQuery").value.trim();
@@ -2850,6 +2947,7 @@
28502947
const activeId = options.activeReportId || (options.keepSelection && state.currentReport ? state.currentReport.report_id : state.reports[0]?.report_id || "");
28512948
renderReportList(state.reports, activeId);
28522949
if (activeId) await loadReportContent(activeId, true);
2950+
else clearReportViewer();
28532951
}
28542952

28552953
async function pollReadingTask(taskId, activeReportId = "") {
@@ -5407,15 +5505,27 @@
54075505
runAction(() => refreshReports({ keepSelection: false }), "筛选报告");
54085506
});
54095507
$("reportsList").addEventListener("click", (event) => {
5508+
hideReportContextMenu();
54105509
const row = event.target.closest("[data-report-id]");
54115510
if (row) runAction(() => loadReportContent(row.dataset.reportId), "打开报告");
54125511
});
54135512
$("reportsList").addEventListener("contextmenu", (event) => {
54145513
const row = event.target.closest("[data-report-id]");
54155514
if (!row) return;
54165515
event.preventDefault();
5417-
toggleReportCardHighlight(row.dataset.reportId);
5516+
const reportId = row.dataset.reportId;
5517+
runAction(async () => {
5518+
await loadReportContent(reportId, true);
5519+
showReportContextMenu(reportId, event.clientX, event.clientY);
5520+
}, "打开报告菜单");
5521+
});
5522+
document.addEventListener("click", (event) => {
5523+
if (!event.target.closest("#reportContextMenu")) hideReportContextMenu();
5524+
});
5525+
document.addEventListener("keydown", (event) => {
5526+
if (event.key === "Escape") hideReportContextMenu();
54185527
});
5528+
window.addEventListener("scroll", hideReportContextMenu, true);
54195529
["openReportAbsBtn", "openReportPdfBtn", "openReportDocBtn"].forEach((id) => {
54205530
$(id).addEventListener("click", () => {
54215531
const href = $(id).dataset.href;

0 commit comments

Comments
 (0)