f4791cfada
D.1: documents route 디자인 토큰 정리 (var(--*) → 시맨틱 토큰, 잔여 0) D.2: isQuestion 질문형 감지 유틸 (? 단일단어 허용, 한/영 6규칙) D.3: AskAnswerCard 컴팩트 답변 카드 + analyze.ts 타입 정의 D.4: 질문형 검색 시 /search/ask 병렬 호출 + 상단 카드 배치 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
62 lines
2.1 KiB
TypeScript
62 lines
2.1 KiB
TypeScript
/**
|
|
* 검색 쿼리가 "질문형"인지 판별 (규칙 기반).
|
|
* true이면 검색 결과 페이지에서 /api/search/ask 병렬 호출.
|
|
*
|
|
* false positive: /ask가 refused=true 반환하면 카드 숨김 → 무해.
|
|
* false negative: 기존 검색 결과만 표시 → 무해.
|
|
*/
|
|
export function isQuestion(q: string): boolean {
|
|
const trimmed = q.trim();
|
|
if (trimmed.length === 0) return false;
|
|
|
|
// 1. ?로 끝나면 단일 단어라도 허용 (왜?, 절차?)
|
|
if (trimmed.endsWith('?')) return true;
|
|
|
|
// ? 없으면 단일 단어 / 4자 미만 제외 (키워드 보호)
|
|
if (trimmed.length < 4) return false;
|
|
if (trimmed.split(/\s+/).length < 2) return false;
|
|
|
|
// 2. 한국어 질문 어미
|
|
const KO_ENDINGS = [
|
|
'인가요', '인가', '인지', '있나요', '있나',
|
|
'할까요', '할까', '될까요', '될까',
|
|
'뭐야', '뭔가', '뭘까', '뭔지', '뭐지', '뭐죠',
|
|
'는지', '은지', '일까',
|
|
'어때', '어떤가',
|
|
'됩니까', '합니까', '입니까', '나요', '까요',
|
|
'주세요', '알려줘', '설명해', '비교해',
|
|
];
|
|
for (const ending of KO_ENDINGS) {
|
|
if (trimmed.endsWith(ending)) return true;
|
|
}
|
|
|
|
// 3. 한국어 질문 시작어
|
|
const KO_STARTS = [
|
|
'어떻게', '왜', '무엇', '무슨', '뭐가', '누가',
|
|
'어디', '언제', '몇', '얼마나', '어떤', '어느',
|
|
];
|
|
for (const start of KO_STARTS) {
|
|
if (trimmed.startsWith(start)) return true;
|
|
}
|
|
|
|
// 4. 영어 질문 시작어 (대소문자 무시)
|
|
const EN_STARTS = [
|
|
'what', 'how', 'why', 'when', 'where', 'who', 'which',
|
|
'is', 'are', 'do', 'does', 'can', 'could', 'should', 'would',
|
|
'explain', 'describe', 'compare', 'tell me',
|
|
];
|
|
const lower = trimmed.toLowerCase();
|
|
for (const start of EN_STARTS) {
|
|
if (lower.startsWith(start + ' ')) return true;
|
|
}
|
|
|
|
// 5. 의미 패턴 (끝 단어)
|
|
const SEMANTIC_ENDINGS = ['차이', '비교', '설명', '요약', '정리', '방법', '절차'];
|
|
const lastWord = trimmed.split(/\s+/).pop() || '';
|
|
for (const pat of SEMANTIC_ENDINGS) {
|
|
if (lastWord === pat || lastWord.endsWith(pat)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|