fix(web): extract single tryGetHostname helper (DRY, unified fallback)

Fixes #1368

Previously,  was duplicated in 4 places with 3 subtly different fallback behaviors:
1. inline-citation.tsx: returned  on error
2. markdown-text.tsx: returned  on error
3. assistant-message.tsx: returned  on error
4. citation.tsx: returned  on error

Created canonical  in  that:
- Returns
- Strips  prefix from hostname
- Returns  on invalid URL (safest contract)

Updated all 4 call sites:
- inline-citation.tsx:  (preserves original fallback)
- markdown-text.tsx:  (preserves original fallback)
- assistant-message.tsx:  (drop-in, both return )
- citation.tsx:  (drop-in, both return )

Co-authored-by: guangyang1206 <guangyang1206@users.noreply.github.com>
This commit is contained in:
guangyang1206 2026-05-16 12:15:16 +08:00
parent 1119f557df
commit f096548a16
5 changed files with 24 additions and 38 deletions

View file

@ -24,6 +24,8 @@ import dynamic from "next/dynamic";
import type { FC } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { commentsEnabledAtom, targetCommentIdAtom } from "@/atoms/chat/current-thread.atom";
import { tryGetHostname } from "@/lib/url";
import {
globalNewLLMConfigsAtom,
newLLMConfigsAtom,
@ -99,20 +101,12 @@ const GenerateImageToolUI = dynamic(
import("@/components/tool-ui/generate-image").then((m) => ({ default: m.GenerateImageToolUI })),
{ ssr: false }
);
function extractDomain(url: string): string | undefined {
try {
return new URL(url).hostname.replace(/^www\./, "");
} catch {
return undefined;
}
}
function useCitationsFromMetadata(): SerializableCitation[] {
const allCitations = useAllCitationMetadata();
return useMemo(() => {
const result: SerializableCitation[] = [];
for (const [url, meta] of allCitations) {
const domain = extractDomain(url);
const domain = tryGetHostname(url);
result.push({
id: `url-cite-${url}`,
href: url,

View file

@ -193,14 +193,7 @@ const SurfsenseDocCitation: FC<{ chunkId: number }> = ({ chunkId }) => {
);
};
function extractDomain(url: string): string {
try {
const hostname = new URL(url).hostname;
return hostname.replace(/^www\./, "");
} catch {
return url;
}
}
import { tryGetHostname } from "@/lib/url";
interface UrlCitationProps {
url: string;
@ -212,7 +205,7 @@ interface UrlCitationProps {
* page title and snippet (extracted deterministically from web_search tool results).
*/
export const UrlCitation: FC<UrlCitationProps> = ({ url }) => {
const domain = extractDomain(url);
const domain = tryGetHostname(url) ?? url;
const meta = useCitationMetadata(url);
return (

View file

@ -23,6 +23,8 @@ import "katex/dist/katex.min.css";
import { toast } from "sonner";
import { processChildrenWithCitations } from "@/components/citations/citation-renderer";
import { Skeleton } from "@/components/ui/skeleton";
import { tryGetHostname } from "@/lib/url";
import {
Table,
TableBody,
@ -139,15 +141,6 @@ const MarkdownTextImpl = () => {
export const MarkdownText = memo(MarkdownTextImpl);
function extractDomain(url: string): string {
try {
const parsed = new URL(url);
return parsed.hostname.replace(/^www\./, "");
} catch {
return "";
}
}
// Canonical local-file virtual paths are mount-prefixed: /<mount>/<relative/path>
const LOCAL_FILE_PATH_REGEX = /^\/[a-z0-9_-]+\/[^\s`]+(?:\/[^\s`]+)*$/;
@ -288,7 +281,7 @@ function FilePathLink({ path, className }: { path: string; className?: string })
function MarkdownImage({ src, alt }: { src?: string; alt?: string }) {
if (!src) return null;
const domain = extractDomain(src);
const domain = tryGetHostname(src) ?? "";
return (
<div className="my-4 w-fit max-w-lg overflow-hidden rounded-2xl border bg-muted/30 select-none">

View file

@ -6,19 +6,11 @@ import * as React from "react";
import { openSafeNavigationHref, sanitizeHref } from "../shared/media";
import { cn, Popover, PopoverContent, PopoverTrigger } from "./_adapter";
import type { CitationVariant, SerializableCitation } from "./schema";
import { tryGetHostname } from "@/lib/url";
import { TYPE_ICONS } from "./type-icons";
const FALLBACK_LOCALE = "en-US";
function extractDomain(url: string): string | undefined {
try {
const urlObj = new URL(url);
return urlObj.hostname.replace(/^www\./, "");
} catch {
return undefined;
}
}
function formatDate(isoString: string, locale: string): string {
try {
const date = new Date(isoString);
@ -78,7 +70,7 @@ export function Citation(props: CitationProps) {
const locale = providedLocale ?? FALLBACK_LOCALE;
const sanitizedHref = sanitizeHref(rawHref);
const domain = providedDomain ?? extractDomain(rawHref);
const domain = providedDomain ?? tryGetHostname(rawHref);
const citationData: SerializableCitation = {
...serializable,