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)를 사용하여 응답을 스트리밍하지만, UI는 자연스럽게 느껴지는 방식으로 콘텐츠를 점진적으로 표시합니다.
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: 'Searching the web...' },
reading: { icon: <BookIcon />, text: 'Reading sources...' },
writing: { icon: <PenIcon />, text: 'Writing answer...' },
};
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: "I found multiple meanings. Which one did you mean?",
too_broad: "This topic is quite broad. Can you be more specific?",
missing_context: "I need a bit more context to give you a useful answer.",
};
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는 사용자를 더 나은 쿼리로 적극적으로 안내합니다.
컬렉션과 Spaces
체계적인 리서치를 위해 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>
);
}
디자인 인사이트: 컬렉션은 학자와 저널리스트가 수동으로 관리하던 탭 형태의 리서치 스택을 미러링하며, 이를 제품에 직접 내장했습니다.
비주얼 디자인 시스템
타이포그래피: 개성보다 명료함
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는 구조화된 리서치를 위한 전용 컬렉션으로, 사용자가 관련 쿼리를 정리하고, 중요한 결과를 고정하며, 참조 문서를 업로드할 수 있습니다. 학자와 기자들이 수동으로 관리하던 탭으로 구성된 리서치 스택을 미러링하며, 이제 진행 중인 연구 프로젝트를 위해 제품에 직접 내장되어 있습니다.