Perplexity:AI原生搜索设计

Perplexity如何在AI搜索中建立信任:引用优先设计、渐进式披露、流式响应和查询优化。包含TypeScript和CSS实现模式。

5 分钟阅读 175 字
Perplexity:AI原生搜索设计 screenshot

Perplexity

"用户希望尽快获取信息,并且能够信任这些信息。那么,这实际上是一个非常简单的设计问题。"

设计理念

Perplexity 代表了一种新范式:AI 原生搜索。传统搜索返回链接列表,聊天机器人返回对话式回复,而 Perplexity 则将多个来源的信息综合成一个带有引用支持的答案。其设计体现了极致的透明度——每个论断都可追溯到来源。

团队的核心洞察:搜索不是对话,而是一次信息获取任务。UI 设计反映了这一点,呈现的是来源和答案,而非聊天历史和人设。


核心要点

  1. 引用是建立 AI 信任的必要条件 - 每个事实性论断都链接到来源;来源面板始终可见,用户无需离开答案页面即可验证
  2. 熟悉的界面降低使用门槛 - 搜索框看起来像 Google,而非聊天提示框;用户可以输入关键词或完整问题,无需学习对话模式
  3. 预测后续问题 - 大多数用户不知道接下来该问什么;建议与上下文相关的后续问题能保持用户参与度
  4. 展示过程,而非仅展示结果 - "搜索中 → 阅读中 → 撰写中"的阶段展示通过透明度建立信任,并减少感知等待时间
  5. 将弱结果视为失败状态 - 当查询可能产生不充分的结果时,要求用户澄清,而非返回平庸的答案

设计模式库

引用优先设计

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 是用于结构化研究的专用集合,用户可以在其中组织相关查询、固定重要结果和上传参考文档。它们模拟了学者和记者手动维护的标签式研究堆栈,现在直接嵌入产品中用于持续的研究项目。


参考资料