mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-21 13:24:44 +08:00
chore: 移除所有 Citations 相关逻辑,为后续重构做准备
- Backend: 删除 lead_agent / general_purpose 中的 citations_format 与引用相关 reminder;artifacts 下载不再对 markdown 做 citation 清洗,统一走 FileResponse,保留 Response 用于二进制 inline - Frontend: 删除 core/citations 模块、inline-citation、safe-citation-content;新增 MarkdownContent 仅做 Markdown 渲染;消息/artifact 预览与复制均使用原始 content - i18n: 移除 citations 命名空间(loadingCitations、loadingCitationsWithCount) - 技能与 demo: 措辞改为 references,demo 数据去掉 <citations> 块 - 文档: 更新 CLAUDE/AGENTS/README 描述,新增按文件 diff 的代码变更总结 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
import json
|
||||
import mimetypes
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request, Response
|
||||
from fastapi.responses import FileResponse, HTMLResponse, PlainTextResponse
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from fastapi.responses import FileResponse, HTMLResponse, PlainTextResponse, Response
|
||||
|
||||
from src.gateway.path_utils import resolve_thread_virtual_path
|
||||
|
||||
@@ -24,40 +22,6 @@ def is_text_file_by_content(path: Path, sample_size: int = 8192) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _extract_citation_urls(content: str) -> set[str]:
|
||||
"""Extract URLs from <citations> JSONL blocks. Format must match frontend core/citations/utils.ts."""
|
||||
urls: set[str] = set()
|
||||
for match in re.finditer(r"<citations>([\s\S]*?)</citations>", content):
|
||||
for line in match.group(1).split("\n"):
|
||||
line = line.strip()
|
||||
if line.startswith("{"):
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
if "url" in obj:
|
||||
urls.add(obj["url"])
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
pass
|
||||
return urls
|
||||
|
||||
|
||||
def remove_citations_block(content: str) -> str:
|
||||
"""Remove ALL citations from markdown (blocks, [cite-N], and citation links). Used for downloads."""
|
||||
if not content:
|
||||
return content
|
||||
|
||||
citation_urls = _extract_citation_urls(content)
|
||||
|
||||
result = re.sub(r"<citations>[\s\S]*?</citations>", "", content)
|
||||
if "<citations>" in result:
|
||||
result = re.sub(r"<citations>[\s\S]*$", "", result)
|
||||
result = re.sub(r"\[cite-\d+\]", "", result)
|
||||
|
||||
for url in citation_urls:
|
||||
result = re.sub(rf"\[[^\]]+\]\({re.escape(url)}\)", "", result)
|
||||
|
||||
return re.sub(r"\n{3,}", "\n\n", result).strip()
|
||||
|
||||
|
||||
def _extract_file_from_skill_archive(zip_path: Path, internal_path: str) -> bytes | None:
|
||||
"""Extract a file from a .skill ZIP archive.
|
||||
|
||||
@@ -172,24 +136,9 @@ async def get_artifact(thread_id: str, path: str, request: Request) -> FileRespo
|
||||
|
||||
# Encode filename for Content-Disposition header (RFC 5987)
|
||||
encoded_filename = quote(actual_path.name)
|
||||
|
||||
# Check if this is a markdown file that might contain citations
|
||||
is_markdown = mime_type == "text/markdown" or actual_path.suffix.lower() in [".md", ".markdown"]
|
||||
|
||||
|
||||
# if `download` query parameter is true, return the file as a download
|
||||
if request.query_params.get("download"):
|
||||
# For markdown files, remove citations block before download
|
||||
if is_markdown:
|
||||
content = actual_path.read_text()
|
||||
clean_content = remove_citations_block(content)
|
||||
return Response(
|
||||
content=clean_content.encode("utf-8"),
|
||||
media_type="text/markdown",
|
||||
headers={
|
||||
"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}",
|
||||
"Content-Type": "text/markdown; charset=utf-8"
|
||||
}
|
||||
)
|
||||
return FileResponse(path=actual_path, filename=actual_path.name, media_type=mime_type, headers={"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}"})
|
||||
|
||||
if mime_type and mime_type == "text/html":
|
||||
|
||||
Reference in New Issue
Block a user