diff --git a/src/OpenDeepWiki/Models/RepositoryDocResponse.cs b/src/OpenDeepWiki/Models/RepositoryDocResponse.cs index ef9593dc..b352e06e 100644 --- a/src/OpenDeepWiki/Models/RepositoryDocResponse.cs +++ b/src/OpenDeepWiki/Models/RepositoryDocResponse.cs @@ -25,4 +25,15 @@ public class RepositoryDocResponse /// 记录生成此文档时读取的源代码文件路径 /// public List SourceFiles { get; set; } = []; + + /// + /// Repository Git URL, used by the client to build source file links + /// for the correct hosting platform (GitHub, GitLab, Azure DevOps, ...). + /// + public string? GitUrl { get; set; } + + /// + /// Branch the document was generated from, used as the ref in file links. + /// + public string? Branch { get; set; } } diff --git a/src/OpenDeepWiki/Services/Repositories/RepositoryDocsService.cs b/src/OpenDeepWiki/Services/Repositories/RepositoryDocsService.cs index 47e28d4f..2c861936 100644 --- a/src/OpenDeepWiki/Services/Repositories/RepositoryDocsService.cs +++ b/src/OpenDeepWiki/Services/Repositories/RepositoryDocsService.cs @@ -416,6 +416,8 @@ public async Task GetDocAsync(string owner, string repo, Slug = normalizedSlug, Content = docFile.Content, SourceFiles = sourceFiles, + GitUrl = repository.GitUrl, + Branch = branchEntity.BranchName, Exists = true }; } diff --git a/web/app/[owner]/[repo]/[...slug]/page.tsx b/web/app/[owner]/[repo]/[...slug]/page.tsx index 39830f86..df4d8130 100644 --- a/web/app/[owner]/[repo]/[...slug]/page.tsx +++ b/web/app/[owner]/[repo]/[...slug]/page.tsx @@ -176,9 +176,10 @@ export default async function RepoDocPage({ params, searchParams }: RepoDocPageP dangerouslySetInnerHTML={{ __html: safeJsonLd(jsonLd) }} /> - {headings.length > 0 && ( diff --git a/web/components/repo/source-files.tsx b/web/components/repo/source-files.tsx index 4a265f36..a8e4455e 100644 --- a/web/components/repo/source-files.tsx +++ b/web/components/repo/source-files.tsx @@ -1,9 +1,8 @@ "use client"; import { FileCode2, ExternalLink } from "lucide-react"; -import { useParams, useSearchParams } from "next/navigation"; +import { useSearchParams } from "next/navigation"; import { useMemo } from "react"; -import { decodeRouteSegment } from "@/lib/repo-route"; interface SourceFilesProps { files: string[]; @@ -12,20 +11,26 @@ interface SourceFilesProps { } /** - * 构建文件的 Git 平台链接 + * Build a link to a file on the repository's Git hosting platform. + * Returns an empty string when the repository URL is unknown. */ function buildFileUrl(gitUrl: string, branch: string, filePath: string): string { - // 规范化 URL + // Without a known repository URL we cannot build a reliable link + if (!gitUrl) { + return ""; + } + + // Normalize the URL let normalizedUrl = gitUrl.replace(/\.git$/, "").trim(); - - // 转换 SSH 格式为 HTTPS + + // Convert SSH format to HTTPS if (normalizedUrl.startsWith("git@")) { normalizedUrl = normalizedUrl.replace("git@", "https://").replace(":", "/"); } - + normalizedUrl = normalizedUrl.replace(/\/$/, ""); - - // 根据平台构建 URL + + // Build the URL according to the hosting platform if (normalizedUrl.includes("github.com")) { return `${normalizedUrl}/blob/${branch}/${filePath}`; } else if (normalizedUrl.includes("gitlab.com") || normalizedUrl.includes("gitlab")) { @@ -34,23 +39,23 @@ function buildFileUrl(gitUrl: string, branch: string, filePath: string): string return `${normalizedUrl}/blob/${branch}/${filePath}`; } else if (normalizedUrl.includes("bitbucket.org")) { return `${normalizedUrl}/src/${branch}/${filePath}`; + } else if (normalizedUrl.includes("dev.azure.com") || normalizedUrl.includes("visualstudio.com")) { + // Azure DevOps: https://dev.azure.com/org/project/_git/repo?version=GBbranch&path=/path + return `${normalizedUrl}?version=GB${branch}&path=/${filePath}`; } - - // 默认使用 GitHub 格式 + + // Default: assume a GitHub-like format return `${normalizedUrl}/blob/${branch}/${filePath}`; } export function SourceFiles({ files, gitUrl, branch }: SourceFilesProps) { - const params = useParams(); const searchParams = useSearchParams(); - - // 从 URL 参数获取仓库信息 - const owner = decodeRouteSegment(params.owner as string); - const repo = decodeRouteSegment(params.repo as string); + + // Resolve the branch to link against const currentBranch = searchParams.get("branch") || branch || "main"; - - // 构建默认的 Git URL - const defaultGitUrl = gitUrl || `https://github.com/${owner}/${repo}`; + + // Only build links when the real repository URL is known + const repoGitUrl = gitUrl ?? ""; // 对文件进行分组(按目录) const groupedFiles = useMemo(() => { @@ -97,8 +102,22 @@ export function SourceFiles({ files, gitUrl, branch }: SourceFilesProps) {
{dirFiles.map(file => { const fileName = file.split("/").pop() || file; - const fileUrl = buildFileUrl(defaultGitUrl, currentBranch, file); - + const fileUrl = buildFileUrl(repoGitUrl, currentBranch, file); + + // Render a plain label when we cannot build a valid link + if (!fileUrl) { + return ( + + + {fileName} + + ); + } + return (