Merge upstream/experimental and resolve conflicts; citations + path_utils + mode-hover

## 冲突解决 (Resolve conflicts)
- input-box.tsx: 保留 ModeHoverGuide 包裹的模式选择器(PR #26 的 mode-hover-guide)
- message-group.tsx: 保留 getCleanContent / hasCitationsBlock / useParsedCitations
- message-list-item.tsx: 保留 useParsedCitations,移除重复的 MessageLink(使用 CitationAwareLink)
- artifact-file-detail.tsx: 保留 CitationAwareLink、useParsedCitations、contentWithoutCitationsFromParsed
- artifacts.py: 保留 path_utils 与 _extract_citation_urls + remove_citations_block 精简实现
- citations/index.ts: 保留并补充 contentWithoutCitationsFromParsed 导出
- en-US.ts: 保留 Ultra 模式描述 "Reasoning, planning and execution with subagents..."
- zh-CN.ts: 保留「超级」标签,描述保留「思考、计划并执行,可调用子代理分工协作...」

## PR #26 代码改动汇总

### 1. Citations(引用)
- lead_agent prompt: 增加 Web search 与子代理合成时的 citation 提示
- general_purpose: 子代理 system prompt 增加 <citations_format> 说明
- frontend utils: 新增 contentWithoutCitationsFromParsed,removeAllCitations 基于单次解析
- frontend artifact: 使用 contentWithoutCitationsFromParsed(parsed) 避免对同一内容解析两次
- backend artifacts: _extract_citation_urls + remove_citations_block,json 提到顶部

### 2. path_utils(路径解析)
- 新增 backend/src/gateway/path_utils.py:resolve_thread_virtual_path,防 path traversal
- artifacts.py / skills.py:删除内联路径解析,统一使用 path_utils

### 3. Mode hover guide
- input-box: 模式选择器外包 ModeHoverGuide,悬停展示模式说明

### 4. i18n
- en: ultraModeDescription 与 zh: ultraMode / ultraModeDescription 与上游对齐并保留 PR 文案

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ruitanglin
2026-02-09 13:01:01 +08:00
4 changed files with 25 additions and 24 deletions

View File

@@ -34,7 +34,11 @@ import { CodeEditor } from "@/components/workspace/code-editor";
import { useArtifactContent } from "@/core/artifacts/hooks";
import { urlOfArtifact } from "@/core/artifacts/utils";
import type { Citation } from "@/core/citations";
import { removeAllCitations, useParsedCitations } from "@/core/citations";
import {
contentWithoutCitationsFromParsed,
removeAllCitations,
useParsedCitations,
} from "@/core/citations";
import { useI18n } from "@/core/i18n/hooks";
import { installSkill } from "@/core/skills/api";
import { streamdownPlugins } from "@/core/streamdown";
@@ -96,12 +100,10 @@ export function ArtifactFileDetail({
);
const cleanContent =
language === "markdown" && content ? parsed.cleanContent : (content ?? "");
const contentWithoutCitations = useMemo(() => {
if (language === "markdown" && content) {
return removeAllCitations(content);
}
return content;
}, [content, language]);
const contentWithoutCitations =
language === "markdown" && content
? contentWithoutCitationsFromParsed(parsed)
: (content ?? "");
const [viewMode, setViewMode] = useState<"code" | "preview">("code");
const [isInstalling, setIsInstalling] = useState(false);

View File

@@ -1,4 +1,5 @@
export {
contentWithoutCitationsFromParsed,
extractDomainFromUrl,
getCleanContent,
hasCitationsBlock,

View File

@@ -185,27 +185,25 @@ export function isCitationsBlockIncomplete(content: string): boolean {
}
/**
* Remove ALL citations from content, including:
* - <citations> blocks
* - [cite-N] references (and their converted markdown links)
*
* Uses parseCitations once, then strips citation links from cleanContent.
* Used for copy/download to produce content without any citation references.
*
* @param content - The raw content that may contain citations
* @returns Content with all citations completely removed
* Strip citation markdown links from already-cleaned content (from parseCitations).
* Use when you already have ParseCitationsResult to avoid parsing twice.
*/
export function removeAllCitations(content: string): string {
if (!content) return content;
const parsed = parseCitations(content);
export function contentWithoutCitationsFromParsed(
parsed: ParseCitationsResult,
): string {
const citationUrls = new Set(parsed.citations.map((c) => c.url));
// Remove markdown links that point to citation URLs; keep non-citation links
const withoutLinks = parsed.cleanContent.replace(
/\[([^\]]+)\]\(([^)]+)\)/g,
(fullMatch, _text, url) => (citationUrls.has(url) ? "" : fullMatch),
);
return withoutLinks.replace(/\n{3,}/g, "\n\n").trim();
}
/**
* Remove ALL citations from content (blocks, [cite-N], and citation links).
* Used for copy/download. For display you typically use parseCitations/useParsedCitations.
*/
export function removeAllCitations(content: string): string {
if (!content) return content;
return contentWithoutCitationsFromParsed(parseCitations(content));
}

View File

@@ -77,7 +77,7 @@ export const zhCN: Translations = {
reasoningModeDescription: "思考后再行动,在时间与准确性之间取得平衡",
proMode: "Pro",
proModeDescription: "思考、计划再执行,获得更精准的结果,可能需要更多时间",
ultraMode: "Ultra",
ultraMode: "超级",
ultraModeDescription:
"思考、计划并执行,可调用子代理分工协作,适合复杂多步骤任务,能力最强",
searchModels: "搜索模型...",