@solapi/mcp-server
Version:
MCP server for SOLAPI document search and integration
353 lines • 16.2 kB
JavaScript
export class SdkIndexManager {
static instance;
index;
examples = new Map();
synonyms = new Map();
constructor() {
this.index = {
keywordIndex: new Map(),
categoryIndex: new Map(),
languageIndex: new Map(),
titleIndex: new Map(),
descriptionIndex: new Map()
};
this.initializeSynonyms();
}
static getInstance() {
if (!SdkIndexManager.instance) {
SdkIndexManager.instance = new SdkIndexManager();
}
return SdkIndexManager.instance;
}
/**
* 유사어 사전을 초기화합니다.
*/
initializeSynonyms() {
// SMS 관련 유사어
this.synonyms.set('sms', ['단문', '메시지', '문자', '단문메시지', '단문 메시지']);
this.synonyms.set('단문', ['sms', '메시지', '문자', '단문메시지', '단문 메시지']);
this.synonyms.set('메시지', ['sms', '단문', '문자', '메세지']);
this.synonyms.set('문자', ['sms', '단문', '메시지', '메세지']);
// LMS 관련 유사어
this.synonyms.set('lms', ['장문', '장문메시지', '장문 메시지', '긴메시지', '긴 메시지']);
this.synonyms.set('장문', ['lms', '장문메시지', '장문 메시지', '긴메시지', '긴 메시지']);
this.synonyms.set('장문메시지', ['lms', '장문', '장문 메시지', '긴메시지', '긴 메시지']);
// 알림톡 관련 유사어
this.synonyms.set('알림톡', ['카카오톡', '카톡', '알림톡메시지', '알림톡 메시지']);
this.synonyms.set('카카오톡', ['알림톡', '카톡', '알림톡메시지', '알림톡 메시지']);
this.synonyms.set('카톡', ['알림톡', '카카오톡', '알림톡메시지', '알림톡 메시지']);
// 대량발송 관련 유사어
this.synonyms.set('대량발송', ['대량', '발송', '대량 발송', '대량메시지', '대량 메시지', '그룹발송', '그룹 발송']);
this.synonyms.set('대량', ['대량발송', '발송', '대량 발송', '대량메시지', '대량 메시지', '그룹발송', '그룹 발송']);
this.synonyms.set('발송', ['대량발송', '대량', '대량 발송', '전송', '보내기']);
this.synonyms.set('그룹발송', ['대량발송', '대량', '그룹 발송', '대량 발송']);
this.synonyms.set('그룹', ['그룹발송', '그룹 발송', '대량발송', '대량']);
// 예약발송 관련 유사어
this.synonyms.set('예약발송', ['예약', '예약 발송', '스케줄', '스케줄발송', '스케줄 발송', '지연발송', '지연 발송']);
this.synonyms.set('예약', ['예약발송', '예약 발송', '스케줄', '스케줄발송', '스케줄 발송']);
this.synonyms.set('스케줄', ['예약발송', '예약', '스케줄발송', '스케줄 발송']);
// 웹훅 관련 유사어
this.synonyms.set('웹훅', ['webhook', '콜백', 'callback', '웹훅처리', '웹훅 처리']);
this.synonyms.set('webhook', ['웹훅', '콜백', 'callback', '웹훅처리', '웹훅 처리']);
this.synonyms.set('콜백', ['웹훅', 'webhook', 'callback', '웹훅처리', '웹훅 처리']);
// 에러처리 관련 유사어
this.synonyms.set('에러처리', ['에러', '오류', '오류처리', '오류 처리', '예외처리', '예외 처리', '핸들링']);
this.synonyms.set('에러', ['에러처리', '오류', '오류처리', '오류 처리', '예외처리', '예외 처리']);
this.synonyms.set('오류', ['에러처리', '에러', '오류처리', '오류 처리', '예외처리', '예외 처리']);
// 상태조회 관련 유사어
this.synonyms.set('상태조회', ['상태', '조회', '상태 조회', '결과조회', '결과 조회', '발송결과', '발송 결과']);
this.synonyms.set('상태', ['상태조회', '조회', '상태 조회', '결과조회', '결과 조회']);
this.synonyms.set('조회', ['상태조회', '상태', '상태 조회', '결과조회', '결과 조회']);
// 계정관리 관련 유사어
this.synonyms.set('계정관리', ['계정', '관리', '계정 관리', '잔액', '포인트', '잔액조회', '잔액 조회']);
this.synonyms.set('계정', ['계정관리', '관리', '계정 관리', '잔액', '포인트']);
this.synonyms.set('잔액', ['계정관리', '계정', '포인트', '잔액조회', '잔액 조회', 'balance']);
// 언어별 유사어
this.synonyms.set('nodejs', ['node.js', 'node', 'javascript', 'js', '자바스크립트']);
this.synonyms.set('node.js', ['nodejs', 'node', 'javascript', 'js', '자바스크립트']);
this.synonyms.set('javascript', ['nodejs', 'node.js', 'node', 'js', '자바스크립트']);
this.synonyms.set('js', ['nodejs', 'node.js', 'node', 'javascript', '자바스크립트']);
this.synonyms.set('java', ['자바', '자바언어']);
this.synonyms.set('python', ['파이썬', '파이선']);
this.synonyms.set('go', ['golang', '고언어']);
this.synonyms.set('golang', ['go', '고언어']);
}
/**
* 모든 예제 데이터로 인덱스를 구축합니다.
*/
buildIndex(examples) {
// 기존 인덱스 초기화
this.clearIndex();
// 예제 데이터 저장
examples.forEach(example => {
this.examples.set(example.id, example);
this.addToIndex(example);
});
}
/**
* 단일 예제를 인덱스에 추가합니다.
*/
addToIndex(example) {
// 키워드 인덱스
example.keywords.forEach(keyword => {
const normalizedKeyword = keyword.toLowerCase();
if (!this.index.keywordIndex.has(normalizedKeyword)) {
this.index.keywordIndex.set(normalizedKeyword, new Set());
}
this.index.keywordIndex.get(normalizedKeyword).add(example.id);
});
// 카테고리 인덱스
const normalizedCategory = example.category.toLowerCase();
if (!this.index.categoryIndex.has(normalizedCategory)) {
this.index.categoryIndex.set(normalizedCategory, new Set());
}
this.index.categoryIndex.get(normalizedCategory).add(example.id);
// 언어 인덱스 (키워드 기반으로 추론)
const language = this.detectLanguage(example);
if (!this.index.languageIndex.has(language)) {
this.index.languageIndex.set(language, new Set());
}
this.index.languageIndex.get(language).add(example.id);
// 제목 인덱스 (단어별로 분리)
const titleWords = this.extractWords(example.title);
titleWords.forEach(word => {
if (!this.index.titleIndex.has(word)) {
this.index.titleIndex.set(word, new Set());
}
this.index.titleIndex.get(word).add(example.id);
});
// 설명 인덱스 (단어별로 분리)
const descriptionWords = this.extractWords(example.description);
descriptionWords.forEach(word => {
if (!this.index.descriptionIndex.has(word)) {
this.index.descriptionIndex.set(word, new Set());
}
this.index.descriptionIndex.get(word).add(example.id);
});
}
/**
* 예제의 언어를 감지합니다.
*/
detectLanguage(example) {
if (example.keywords.some(k => k.toLowerCase().includes('javascript') ||
k.toLowerCase().includes('nodejs') ||
k.toLowerCase().includes('js') ||
k.toLowerCase().includes('node'))) {
return 'javascript';
}
if (example.keywords.some(k => k.toLowerCase().includes('java') ||
k.toLowerCase().includes('kotlin'))) {
return example.keywords.some(k => k.toLowerCase().includes('kotlin')) ? 'kotlin' : 'java';
}
if (example.keywords.some(k => k.toLowerCase().includes('python'))) {
return 'python';
}
if (example.keywords.some(k => k.toLowerCase().includes('go') ||
k.toLowerCase().includes('golang'))) {
return 'go';
}
if (example.keywords.some(k => k.toLowerCase().includes('asp') ||
k.toLowerCase().includes('vbscript'))) {
return 'asp';
}
return 'javascript'; // 기본값
}
/**
* 텍스트에서 검색 가능한 단어들을 추출합니다.
*/
extractWords(text) {
return text
.toLowerCase()
.replace(/[^\w\s가-힣]/g, ' ') // 특수문자 제거, 한글 포함
.split(/\s+/)
.filter(word => word.length > 1) // 1글자 단어 제외
.filter(word => !this.isStopWord(word)); // 불용어 제거
}
/**
* 불용어를 확인합니다.
*/
isStopWord(word) {
const stopWords = new Set([
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by',
'이', '그', '저', '의', '를', '을', '가', '은', '는', '에', '에서', '로', '으로', '와', '과'
]);
return stopWords.has(word);
}
/**
* 키워드로 예제 ID들을 검색합니다. (유사어 및 부분 매칭 지원)
*/
searchByKeyword(keyword) {
const normalizedKeyword = keyword.toLowerCase().trim();
const results = new Set();
// 1. 정확한 매치
const exactMatch = this.index.keywordIndex.get(normalizedKeyword);
if (exactMatch) {
exactMatch.forEach(id => results.add(id));
}
// 2. 유사어 매치
const synonyms = this.synonyms.get(normalizedKeyword) || [];
synonyms.forEach(synonym => {
const synonymMatch = this.index.keywordIndex.get(synonym);
if (synonymMatch) {
synonymMatch.forEach(id => results.add(id));
}
});
// 3. 부분 매치 (양방향)
for (const [indexedKeyword, exampleIds] of this.index.keywordIndex) {
// 검색어가 인덱스 키워드에 포함되는 경우
if (indexedKeyword.includes(normalizedKeyword)) {
exampleIds.forEach(id => results.add(id));
}
// 인덱스 키워드가 검색어에 포함되는 경우
if (normalizedKeyword.includes(indexedKeyword)) {
exampleIds.forEach(id => results.add(id));
}
}
// 4. 유사어의 부분 매치도 검색
synonyms.forEach(synonym => {
for (const [indexedKeyword, exampleIds] of this.index.keywordIndex) {
if (indexedKeyword.includes(synonym) || synonym.includes(indexedKeyword)) {
exampleIds.forEach(id => results.add(id));
}
}
});
return Array.from(results);
}
/**
* 카테고리로 예제 ID들을 검색합니다.
*/
searchByCategory(category) {
const normalizedCategory = category.toLowerCase();
const categorySet = this.index.categoryIndex.get(normalizedCategory);
return categorySet ? Array.from(categorySet) : [];
}
/**
* 언어로 예제 ID들을 검색합니다.
*/
searchByLanguage(language) {
const normalizedLanguage = language.toLowerCase();
const languageSet = this.index.languageIndex.get(normalizedLanguage);
return languageSet ? Array.from(languageSet) : [];
}
/**
* 제목에서 단어로 검색합니다.
*/
searchByTitle(word) {
const normalizedWord = word.toLowerCase();
const titleSet = this.index.titleIndex.get(normalizedWord);
return titleSet ? Array.from(titleSet) : [];
}
/**
* 설명에서 단어로 검색합니다.
*/
searchByDescription(word) {
const normalizedWord = word.toLowerCase();
const descriptionSet = this.index.descriptionIndex.get(normalizedWord);
return descriptionSet ? Array.from(descriptionSet) : [];
}
/**
* 복합 검색을 수행합니다. (개선된 유사어 및 부분 매칭)
*/
searchComplex(query, category, language) {
const queryWords = this.extractWords(query);
const resultSets = [];
// 1. 전체 쿼리로 키워드 검색
const keywordResults = this.searchByKeyword(query);
if (keywordResults.length > 0) {
resultSets.push(new Set(keywordResults));
}
// 2. 쿼리 단어별 키워드 검색
queryWords.forEach(word => {
const wordResults = this.searchByKeyword(word);
if (wordResults.length > 0) {
resultSets.push(new Set(wordResults));
}
});
// 3. 제목 검색 (개선된 부분 매칭)
queryWords.forEach(word => {
const titleResults = this.searchByTitle(word);
if (titleResults.length > 0) {
resultSets.push(new Set(titleResults));
}
});
// 4. 설명 검색 (개선된 부분 매칭)
queryWords.forEach(word => {
const descriptionResults = this.searchByDescription(word);
if (descriptionResults.length > 0) {
resultSets.push(new Set(descriptionResults));
}
});
// 결과 합치기
let combinedResults = new Set();
if (resultSets.length > 0) {
combinedResults = resultSets.reduce((acc, set) => {
return new Set([...acc, ...set]);
});
}
// 카테고리 필터 적용
if (category) {
const categoryResults = new Set(this.searchByCategory(category));
combinedResults = new Set([...combinedResults].filter(id => categoryResults.has(id)));
}
// 언어 필터 적용
if (language) {
const languageResults = new Set(this.searchByLanguage(language));
combinedResults = new Set([...combinedResults].filter(id => languageResults.has(id)));
}
return Array.from(combinedResults);
}
/**
* 예제 ID로 실제 예제 객체를 반환합니다.
*/
getExampleById(id) {
return this.examples.get(id);
}
/**
* 예제 ID 배열로 실제 예제 객체들을 반환합니다.
*/
getExamplesByIds(ids) {
return ids
.map(id => this.examples.get(id))
.filter((example) => example !== undefined);
}
/**
* 인덱스 통계를 반환합니다.
*/
getIndexStats() {
return {
totalExamples: this.examples.size,
keywordCount: this.index.keywordIndex.size,
categoryCount: this.index.categoryIndex.size,
languageCount: this.index.languageIndex.size,
titleWordCount: this.index.titleIndex.size,
descriptionWordCount: this.index.descriptionIndex.size
};
}
/**
* 모든 인덱스를 초기화합니다.
*/
clearIndex() {
this.index.keywordIndex.clear();
this.index.categoryIndex.clear();
this.index.languageIndex.clear();
this.index.titleIndex.clear();
this.index.descriptionIndex.clear();
this.examples.clear();
}
/**
* 특정 예제를 인덱스에서 제거합니다.
*/
removeFromIndex(exampleId) {
const example = this.examples.get(exampleId);
if (!example)
return;
// 모든 인덱스에서 해당 예제 제거
this.index.keywordIndex.forEach(set => set.delete(exampleId));
this.index.categoryIndex.forEach(set => set.delete(exampleId));
this.index.languageIndex.forEach(set => set.delete(exampleId));
this.index.titleIndex.forEach(set => set.delete(exampleId));
this.index.descriptionIndex.forEach(set => set.delete(exampleId));
this.examples.delete(exampleId);
}
}
//# sourceMappingURL=sdkIndexManager.js.map