Perplexity:AI原生搜索设计
Perplexity如何在AI搜索中建立信任:引用优先设计、渐进式披露、流式响应和查询优化。包含TypeScript和CSS实现模式。
Perplexity
"用户希望尽快获取信息,并且能够信任这些信息。那么,这实际上是一个非常简单的设计问题。"
设计理念
Perplexity 代表了一种新范式:AI 原生搜索。传统搜索返回链接列表,聊天机器人返回对话式回复,而 Perplexity 则将多个来源的信息综合成一个带有引用支持的答案。其设计体现了极致的透明度——每个论断都可追溯到来源。
团队的核心洞察:搜索不是对话,而是一次信息获取任务。UI 设计反映了这一点,呈现的是来源和答案,而非聊天历史和人设。
核心要点
- 引用是建立 AI 信任的必要条件 - 每个事实性论断都链接到来源;来源面板始终可见,用户无需离开答案页面即可验证
- 熟悉的界面降低使用门槛 - 搜索框看起来像 Google,而非聊天提示框;用户可以输入关键词或完整问题,无需学习对话模式
- 预测后续问题 - 大多数用户不知道接下来该问什么;建议与上下文相关的后续问题能保持用户参与度
- 展示过程,而非仅展示结果 - "搜索中 → 阅读中 → 撰写中"的阶段展示通过透明度建立信任,并减少感知等待时间
- 将弱结果视为失败状态 - 当查询可能产生不充分的结果时,要求用户澄清,而非返回平庸的答案
设计模式库
引用优先设计
Perplexity 率先在 AI 回复中使用内联引用,从根本上改变了用户对生成内容的信任方式。每个事实性论断都链接到来源。
interface Citation {
index: number;
url: string;
title: string;
favicon: string;
snippet: string;
domain: string;
}
interface AnswerBlock {
text: string;
citations: number[]; // Indices into citation array
}
function CitedAnswer({ blocks, citations }: {
blocks: AnswerBlock[];
citations: Citation[];
}) {
return (
<article className="answer">
{blocks.map((block, i) => (
<p key={i}>
{block.text}
{block.citations.map(citationIndex => (
<CitationMarker
key={citationIndex}
citation={citations[citationIndex]}
index={citationIndex + 1}
/>
))}
</p>
))}
{/* Source panel always visible */}
<aside className="sources-panel">
<h3>Sources</h3>
{citations.map((citation, i) => (
<SourceCard key={i} citation={citation} index={i + 1} />
))}
</aside>
</article>
);
}
function CitationMarker({ citation, index }: {
citation: Citation;
index: number;
}) {
const [expanded, setExpanded] = useState(false);
return (
<span className="citation-wrapper">
<sup
className="citation-marker"
onMouseEnter={() => setExpanded(true)}
onMouseLeave={() => setExpanded(false)}
>
[{index}]
</sup>
{/* Expandable snippet preview */}
{expanded && (
<div className="citation-preview">
<img src={citation.favicon} alt="" className="citation-favicon" />
<span className="citation-domain">{citation.domain}</span>
<p className="citation-snippet">{citation.snippet}</p>
</div>
)}
</span>
);
}
设计洞察:引用不是可选项,而是融入每次交互之中。来源面板始终可见,用户可以在不离开答案视图的情况下验证论断。
熟悉的搜索界面
Perplexity 的输入框看起来像传统搜索框,而非聊天提示框。这种熟悉的设计降低了不习惯对话式 AI 的用户的使用门槛。
function SearchInput({ onSubmit }: { onSubmit: (query: string) => void }) {
const [query, setQuery] = useState('');
return (
<div className="search-container">
{/* Intentionally looks like Google/traditional search */}
<div className="search-box">
<SearchIcon className="search-icon" />
<input
type="text"
className="search-input"
placeholder="Ask anything..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && onSubmit(query)}
/>
{query && (
<button
className="clear-button"
onClick={() => setQuery('')}
>
<XIcon />
</button>
)}
<button
className="submit-button"
onClick={() => onSubmit(query)}
>
<ArrowRightIcon />
</button>
</div>
{/* Optional context chips */}
<div className="focus-chips">
<Chip icon={<GlobeIcon />}>All</Chip>
<Chip icon={<AcademicIcon />}>Academic</Chip>
<Chip icon={<CodeIcon />}>Code</Chip>
<Chip icon={<VideoIcon />}>Video</Chip>
</div>
</div>
);
}
设计洞察:即使是几个关键词也能起作用。用户无需精心构思复杂的提示词。界面同时接受简单查询和复杂问题。
渐进式披露与追问建议
Perplexity 不期望用户自己想出好的追问,而是预测并主动建议。预测性追问解决了一个现实问题:大多数用户不知道接下来该问什么。
interface FollowUpSuggestion {
question: string;
reasoning: string; // Why this might be relevant
}
function FollowUpSuggestions({
suggestions,
onSelect
}: {
suggestions: FollowUpSuggestion[];
onSelect: (question: string) => void;
}) {
return (
<div className="follow-ups">
<h4>Related</h4>
{/* Show one at a time - progressive disclosure */}
{suggestions.slice(0, 4).map((suggestion, i) => (
<button
key={i}
className="follow-up-chip"
onClick={() => onSelect(suggestion.question)}
>
<span className="follow-up-text">{suggestion.question}</span>
<ArrowRightIcon className="follow-up-arrow" />
</button>
))}
</div>
);
}
// Predict follow-ups based on query context
function generateFollowUps(query: string, answer: string): FollowUpSuggestion[] {
// AI generates contextually relevant next questions
return [
{ question: "How does this compare to alternatives?", reasoning: "comparison" },
{ question: "What are the limitations?", reasoning: "critical analysis" },
{ question: "Can you provide specific examples?", reasoning: "concrete details" },
{ question: "What's the historical context?", reasoning: "background" },
];
}
设计洞察:就像在主题演讲结束时很少有人会提问一样,大多数用户并不擅长提出追问。预测他们可能想了解的内容。
流式响应与渐进式渲染
Perplexity 使用 Server-Sent Events (SSE) 来流式传输响应,但界面以一种自然的方式渐进地展示内容。
function StreamingAnswer({ query }: { query: string }) {
const [sources, setSources] = useState<Citation[]>([]);
const [answer, setAnswer] = useState<string>('');
const [phase, setPhase] = useState<'searching' | 'reading' | 'writing'>('searching');
useEffect(() => {
const eventSource = new EventSource(`/api/search?q=${encodeURIComponent(query)}`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'sources':
setPhase('reading');
setSources(data.sources);
break;
case 'chunk':
setPhase('writing');
setAnswer(prev => prev + data.text);
break;
case 'done':
eventSource.close();
break;
}
};
return () => eventSource.close();
}, [query]);
return (
<div className="streaming-answer">
{/* 阶段指示器 */}
<PhaseIndicator phase={phase} />
{/* 来源先显示 */}
{sources.length > 0 && (
<SourceCards sources={sources} />
)}
{/* 答案流式输入 */}
<div className="answer-content">
<TypewriterText text={answer} />
{phase === 'writing' && <BlinkingCursor />}
</div>
</div>
);
}
function PhaseIndicator({ phase }: { phase: 'searching' | 'reading' | 'writing' }) {
const phases = {
searching: { icon: <SearchIcon />, text: '正在搜索网络...' },
reading: { icon: <BookIcon />, text: '正在阅读来源...' },
writing: { icon: <PenIcon />, text: '正在撰写答案...' },
};
return (
<div className="phase-indicator">
{phases[phase].icon}
<span>{phases[phase].text}</span>
<LoadingDots />
</div>
);
}
设计洞察:展示过程,而不仅仅是结果。用户先看到来源出现(建立信任),然后观看答案的生成过程。透明度能够减少等待时的焦虑感。
通过优化查询预防错误
当查询过于宽泛时,Perplexity 会请求澄清,而不是返回质量不佳的结果。系统将结果不足视为一种失败状态。
interface ClarificationRequest {
type: 'ambiguous' | 'too_broad' | 'missing_context';
suggestions: string[];
originalQuery: string;
}
function QueryRefinement({ request, onRefine }: {
request: ClarificationRequest;
onRefine: (refinedQuery: string) => void;
}) {
const messages = {
ambiguous: "我找到了多种含义。您指的是哪一个?",
too_broad: "这个主题比较宽泛。能否更具体一些?",
missing_context: "我需要更多上下文信息才能给您有用的答案。",
};
return (
<div className="refinement-prompt">
<p className="refinement-message">{messages[request.type]}</p>
<div className="refinement-suggestions">
{request.suggestions.map((suggestion, i) => (
<button
key={i}
className="refinement-option"
onClick={() => onRefine(suggestion)}
>
{suggestion}
</button>
))}
</div>
<div className="refinement-custom">
<input
type="text"
placeholder="Or type your own refinement..."
onKeyDown={(e) => {
if (e.key === 'Enter') {
onRefine((e.target as HTMLInputElement).value);
}
}}
/>
</div>
</div>
);
}
设计洞察:其他搜索服务返回较弱的结果,期望用户自行优化查询。Perplexity 则主动引导用户构建更好的查询。
收藏夹与空间
对于结构化研究,Perplexity 提供了空间(Spaces)功能——专用的收藏集合,用户可以在其中组织查询、固定结果并上传参考资料。
interface Space {
id: string;
name: string;
threads: Thread[];
documents: Document[];
createdAt: Date;
}
interface Thread {
id: string;
query: string;
answer: Answer;
citations: Citation[];
pinned: boolean;
}
function SpacesSidebar({ spaces, activeSpace, onSelect }: {
spaces: Space[];
activeSpace: string;
onSelect: (id: string) => void;
}) {
return (
<aside className="spaces-sidebar">
<header>
<h2>Spaces</h2>
<button className="new-space">
<PlusIcon /> New Space
</button>
</header>
<nav className="space-list">
{spaces.map(space => (
<button
key={space.id}
className={`space-item ${space.id === activeSpace ? 'active' : ''}`}
onClick={() => onSelect(space.id)}
>
<FolderIcon />
<span className="space-name">{space.name}</span>
<span className="thread-count">{space.threads.length}</span>
</button>
))}
</nav>
</aside>
);
}
function SpaceView({ space }: { space: Space }) {
return (
<div className="space-view">
<header className="space-header">
<h1>{space.name}</h1>
<div className="space-actions">
<button><ShareIcon /> Share</button>
<button><UploadIcon /> Add Files</button>
</div>
</header>
{/* 固定的对话线程 */}
<section className="pinned-threads">
<h3>Pinned</h3>
{space.threads.filter(t => t.pinned).map(thread => (
<ThreadCard key={thread.id} thread={thread} />
))}
</section>
{/* 所有对话线程 */}
<section className="all-threads">
<h3>All Threads</h3>
{space.threads.map(thread => (
<ThreadCard key={thread.id} thread={thread} />
))}
</section>
{/* Embedded documents */}
{space.documents.length > 0 && (
<section className="space-documents">
<h3>Reference Materials</h3>
{space.documents.map(doc => (
<DocumentCard key={doc.id} document={doc} />
))}
</section>
)}
</div>
);
}
设计洞察:Collections 模式复刻了学者和记者手动维护的标签式研究资料堆,如今直接嵌入产品之中。
视觉设计系统
字体排印:清晰优先于个性
Perplexity 采用中性、高可读性的字体系统。内容才是主角,而非界面本身。
:root {
/* System font stack for fastest load */
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'SF Mono', Monaco, 'Courier New', monospace;
/* Type scale - optimized for reading */
--text-sm: 13px;
--text-base: 15px;
--text-lg: 17px;
--text-xl: 20px;
--text-2xl: 24px;
/* Line heights - generous for readability */
--leading-tight: 1.3;
--leading-normal: 1.6;
--leading-relaxed: 1.8;
}
/* Answer text - optimized for long-form reading */
.answer-content {
font-size: var(--text-base);
line-height: var(--leading-relaxed);
max-width: 680px; /* Optimal reading width */
}
/* Citations - smaller, unobtrusive */
.citation-marker {
font-size: var(--text-sm);
color: var(--accent-primary);
cursor: pointer;
vertical-align: super;
}
/* Source cards - scannable metadata */
.source-card {
font-size: var(--text-sm);
line-height: var(--leading-tight);
}
.source-domain {
font-family: var(--font-mono);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
色彩系统:以克制建立信任
色彩运用极为克制,主要用于引用标注、状态指示和焦点状态。
:root {
/* Neutral foundation */
--bg-primary: #ffffff;
--bg-secondary: #f7f7f8;
--bg-tertiary: #ededef;
/* Text hierarchy */
--text-primary: #1a1a1b;
--text-secondary: #57575a;
--text-tertiary: #8e8e93;
/* Citation accent - trustworthy blue */
--accent-citation: #0066cc;
--accent-citation-hover: #0052a3;
/* Focus states */
--focus-ring: 0 0 0 2px var(--accent-citation);
/* Phase indicators */
--phase-searching: #f59e0b;
--phase-reading: #3b82f6;
--phase-writing: #10b981;
}
/* Clean, ad-free interface */
.search-results {
background: var(--bg-primary);
padding: 24px;
border-radius: 12px;
}
/* Citation hover state */
.citation-marker:hover {
background: var(--accent-citation);
color: white;
border-radius: 2px;
}
/* Source card - subtle boundary */
.source-card {
border: 1px solid var(--bg-tertiary);
border-radius: 8px;
padding: 12px;
transition: border-color 0.15s ease;
}
.source-card:hover {
border-color: var(--accent-citation);
}
动画模式
阶段过渡
搜索 → 阅读 → 生成各阶段之间的平滑过渡让用户始终了解当前进度。
/* Phase indicator */
.phase-indicator {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--bg-secondary);
border-radius: 20px;
font-size: var(--text-sm);
color: var(--text-secondary);
}
/* Loading dots animation */
.loading-dots {
display: flex;
gap: 4px;
}
.loading-dots span {
width: 4px;
height: 4px;
background: var(--text-tertiary);
border-radius: 50%;
animation: dot-pulse 1.4s infinite ease-in-out;
}
.loading-dots span:nth-child(2) { animation-delay: 0.2s; }
.loading-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes dot-pulse {
0%, 80%, 100% {
transform: scale(0.6);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}
/* 来源卡片在找到时淡入显示 */
.source-card {
animation: fade-in-up 0.3s ease-out;
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 来源卡片交错动画 */
.source-card:nth-child(1) { animation-delay: 0ms; }
.source-card:nth-child(2) { animation-delay: 100ms; }
.source-card:nth-child(3) { animation-delay: 200ms; }
.source-card:nth-child(4) { animation-delay: 300ms; }
流式文本
答案逐字符显示,但以分组块的形式呈现以提高可读性。
function TypewriterText({ text }: { text: string }) {
const [displayedText, setDisplayedText] = useState('');
useEffect(() => {
// 文本已通过 SSE 到达——直接显示
setDisplayedText(text);
}, [text]);
return (
<div className="typewriter">
{displayedText}
</div>
);
}
function BlinkingCursor() {
return (
<span className="cursor" aria-hidden="true">|</span>
);
}
.cursor {
animation: blink 1s step-end infinite;
color: var(--text-secondary);
}
@keyframes blink {
50% { opacity: 0; }
}
经验总结
1. 引用是建立 AI 信任的必要条件
在 AI 界面中,每一个事实性陈述都应该可追溯。Perplexity 将引用可靠性融入每一次交互中,这不是可选或隐藏的功能。
2. 搜索框优于聊天提示
熟悉的搜索界面降低了使用门槛。用户可以输入关键词或完整问题,两者都有效。不要强制用户使用对话模式。
3. 预测后续问题
大多数用户不知道接下来该问什么。主动建议与上下文相关的后续问题,而不是期望用户主导对话。
4. 展示处理过程
流式输出提供的是透明度,而不仅仅是速度。展示"搜索中 → 阅读中 → 撰写中"的阶段能够建立信任并减少感知等待时间。
5. 将弱结果视为失败
当查询可能产生不充分的结果时,应该要求用户细化查询,而不是返回质量欠佳的答案。引导用户提出更好的问题。
6. 简洁界面等于信任信号
无广告、极简的界面传递了一个信号:产品优先考虑信息质量而非商业变现。
常见问题
Perplexity 的引用系统是如何工作的?
Perplexity 答案中的每个事实性陈述都包含行内编号引用,链接到来源 URL。来源面板与答案并排显示,展示网站图标、域名和摘要预览。悬停在引用编号上会展开显示具体的来源上下文。这使得验证变得即时,无需用户深入挖掘。
为什么 Perplexity 看起来像搜索引擎而不是聊天机器人?
团队发现搜索不是对话——而是信息获取任务。熟悉的搜索框界面同时接受简单关键词和复杂问题,无需用户学习对话式提示模式。这降低了那些对 AI 聊天界面感到不适应的用户的使用门槛。
Perplexity 的流式响应方法是什么?
Perplexity 使用 Server-Sent Events (SSE) 分三个阶段逐步展示内容:"搜索中"(查找来源)、"阅读中"(分析来源)和"撰写中"(综合答案)。来源首先显示以建立信任,然后答案才开始流式输出。这种透明度减少了等待期间的焦虑感。
Perplexity 如何处理模糊或宽泛的查询?
当查询可能产生弱结果时,Perplexity 会要求澄清而不是返回平庸的答案。系统会识别模糊术语、过于宽泛的主题或缺失的上下文,然后建议具体的细化方向。用户可以点击建议或输入自己的澄清内容。
什么是 Perplexity Spaces,如何使用?
Spaces 是用于结构化研究的专用集合,用户可以在其中组织相关查询、固定重要结果和上传参考文档。它们模拟了学者和记者手动维护的标签式研究堆栈,现在直接嵌入产品中用于持续的研究项目。