⏱️ 예상 읽기 시간: 15분
서론
NoteGen은 AI를 활용한 혁신적인 크로스플랫폼 마크다운 노트 앱입니다. Tauri2와 Next.js 기반으로 구축되어 데스크톱과 모바일을 모두 지원하며, ChatGPT, Gemini, Ollama 등 다양한 AI 모델과 RAG(Retrieval-Augmented Generation) 기능을 제공합니다.
이 가이드에서는 NoteGen을 소스에서 컴파일하고, Docker 환경을 구성하며, AI 기능을 상세히 살펴보겠습니다.
프로젝트 개요
주요 특징
- 경량: 20MB 미만의 설치 파일
- 크로스플랫폼: Windows, macOS, Linux, iOS, Android 지원
- AI 통합: 다중 AI 모델 지원 (ChatGPT, Gemini, Grok, Ollama, LM Studio 등)
- RAG 기능: 임베딩 모델과 리랭킹 모델 지원
- 오프라인: 로컬 파일 시스템 기반
- 동기화: GitHub, Gitee, WebDAV 지원
기술 스택
- Frontend: Next.js 15, React 19, TypeScript
- Backend: Tauri 2 (Rust)
- UI: Radix UI, Tailwind CSS
- AI: OpenAI SDK, 다중 프로바이더 지원
- Database: SQLite (벡터 데이터베이스)
개발 환경 설정
필수 요구사항
macOS 환경 기준:
# Node.js 및 pnpm 버전 확인
node --version # v22.16.0+
pnpm --version # v10.12.1+
시스템 요구사항:
- Node.js 22.16.0 이상
- Rust 1.70 이상
- pnpm (패키지 매니저)
Rust 설치
# Rust 설치
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.zshrc
# 버전 확인
rustc --version
cargo --version
Tauri CLI 설치
# Tauri CLI 전역 설치
cargo install tauri-cli
# 또는 pnpm으로 설치
pnpm add -g @tauri-apps/cli
소스 컴파일 가이드
1. 저장소 클론
# 프로젝트 클론
git clone https://github.com/codexu/note-gen.git
cd note-gen
# 브랜치 확인 (dev 브랜치 사용)
git checkout dev
2. 의존성 설치
# pnpm으로 의존성 설치 (npm보다 권장)
pnpm install
# build scripts 승인 (필요시)
pnpm approve-builds
3. 개발 서버 실행
# Next.js 개발 서버 (웹 버전)
pnpm dev
# Tauri 개발 앱 실행 (데스크톱 버전)
pnpm tauri dev
4. 프로덕션 빌드
# 웹 빌드
pnpm build
# 데스크톱 앱 빌드
pnpm tauri build
Docker 설정
NoteGen은 기본적으로 Dockerfile을 제공하지 않으므로, 웹 버전용 Docker 설정을 직접 구성해보겠습니다.
Dockerfile 생성
# 멀티스테이지 빌드
FROM node:22-alpine AS builder
# pnpm 설치
RUN npm install -g pnpm
WORKDIR /app
# 패키지 파일 복사
COPY package.json pnpm-lock.yaml ./
# 의존성 설치
RUN pnpm install --frozen-lockfile
# 소스 코드 복사
COPY . .
# 빌드
RUN pnpm build
# 프로덕션 스테이지
FROM node:22-alpine AS runner
RUN npm install -g pnpm serve
WORKDIR /app
# 프로덕션 의존성 설치
# COPY package.json pnpm-lock.yaml ./
# RUN pnpm install --prod --frozen-lockfile
# 빌드 결과물 복사
# COPY --from=builder /app/.next ./.next
# COPY --from=builder /app/public ./public
# COPY --from=builder /app/next.config.ts ./
# 정적 파일 서빙을 위한 빌드 결과물 복사
COPY --from=builder /app/out ./
# 포트 설정
EXPOSE 3456
# 서버 시작
# CMD ["pnpm", "start"]
# 정적 파일 서버 시작
CMD ["serve", "-s", ".", "-p", "3456"]
Docker Compose 설정
# docker-compose.yml
version: '3.8'
services:
notegen:
build: .
ports:
- "3456:3456"
environment:
- NODE_ENV=production
volumes:
# 노트 데이터 영속화
- notegen_data:/app/data
restart: unless-stopped
volumes:
notegen_data:
Docker 실행
# 이미지 빌드 및 실행
docker-compose up -d
# 또는 직접 빌드
docker build -t notegen .
docker run -p 3456:3456 -v notegen_data:/app/data notegen
AI 기능 상세 분석
지원 AI 모델
NoteGen은 다양한 AI 프로바이더를 지원합니다:
1. 클라우드 AI 서비스
// src/app/core/setting/config.tsx에서 확인
const aiProviders = [
{
key: 'chatgpt',
title: 'ChatGPT',
baseURL: 'https://api.openai.com/v1',
apiKeyUrl: 'https://platform.openai.com/api-keys'
},
{
key: 'gemini',
title: 'Gemini',
baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',
apiKeyUrl: 'https://aistudio.google.com/app/apikey'
},
{
key: 'grok',
title: 'Grok',
baseURL: 'https://api.x.ai/v1',
apiKeyUrl: 'https://console.x.ai/'
},
{
key: 'deepseek',
title: 'DeepSeek',
baseURL: 'https://api.deepseek.com',
apiKeyUrl: 'https://platform.deepseek.com/api_keys'
}
]
2. 로컬 AI 서비스
const localProviders = [
{
key: 'ollama',
title: 'Ollama',
baseURL: 'http://localhost:11434/v1'
},
{
key: 'lmstudio',
title: 'LM Studio',
baseURL: 'http://localhost:1234/v1'
}
]
AI 클라이언트 생성
// src/lib/ai.ts
export async function createOpenAIClient(aiConfig?: AiConfig) {
const config = aiConfig || await getAISettings()
if (!config) throw new Error('AI 설정을 찾을 수 없습니다')
const { baseURL, apiKey, temperature = 0.7, topP = 1 } = config
return new OpenAI({
baseURL,
apiKey,
defaultQuery: { temperature, top_p: topP },
dangerouslyAllowBrowser: true
})
}
스트리밍 응답 처리
export async function fetchAiStream(
text: string,
onUpdate: (content: string) => void,
abortSignal?: AbortSignal
): Promise<string> {
const { messages } = await prepareMessages(text)
const openai = await createOpenAIClient()
const stream = await openai.chat.completions.create({
messages,
stream: true,
signal: abortSignal
})
let fullContent = ''
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || ''
fullContent += content
onUpdate(fullContent)
}
return fullContent
}
RAG (Retrieval-Augmented Generation) 기능
벡터 임베딩
// src/lib/rag.ts
export async function processMarkdownFile(
filePath: string,
fileContent?: string
): Promise<boolean> {
try {
const content = fileContent || await readTextFile(filePath)
// 텍스트 청킹
const chunks = chunkText(content, 1000, 200)
const filename = filePath.split('/').pop() || filePath
// 기존 벡터 삭제
await deleteVectorDocumentsByFilename(filename)
// 각 청크에 대해 임베딩 생성
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i]
const embedding = await fetchEmbedding(chunk)
if (embedding) {
await upsertVectorDocument({
filename,
chunk_id: i,
content: chunk,
embedding: JSON.stringify(embedding),
updated_at: Date.now()
})
}
}
return true
} catch (error) {
console.error(`파일 처리 실패: ${filePath}`, error)
return false
}
}
텍스트 청킹 알고리즘
export function chunkText(
text: string,
chunkSize: number = 1000,
chunkOverlap: number = 200
): string[] {
const chunks: string[] = []
if (text.length <= chunkSize) {
chunks.push(text)
return chunks
}
// 문단 단위로 분할
const paragraphs = text.split('\n\n')
let currentChunk = ''
for (const paragraph of paragraphs) {
if (currentChunk.length + paragraph.length + 2 > chunkSize) {
if (currentChunk.length > 0) {
chunks.push(currentChunk)
// 오버랩 부분 계산
const overlapLength = Math.min(chunkOverlap, currentChunk.length)
const lastChunkParts = currentChunk.split('\n\n')
const overlapParts = []
let currentLength = 0
for (let i = lastChunkParts.length - 1; i >= 0; i--) {
const part = lastChunkParts[i]
if (currentLength + part.length + 2 <= overlapLength) {
overlapParts.unshift(part)
currentLength += part.length + 2
} else {
break
}
}
currentChunk = overlapParts.join('\n\n')
}
currentChunk += paragraph + '\n\n'
} else {
currentChunk += paragraph + '\n\n'
}
}
if (currentChunk.trim()) {
chunks.push(currentChunk.trim())
}
return chunks
}
컨텍스트 검색
export async function getContextForQuery(query: string): Promise<string> {
try {
// 쿼리 임베딩 생성
const queryEmbedding = await fetchEmbedding(query)
if (!queryEmbedding) return ''
// 유사한 문서 검색 (코사인 유사도)
const similarDocs = await getSimilarDocuments(queryEmbedding, 5)
// 리랭킹 적용 (가능한 경우)
const rerankModelAvailable = await checkRerankModelAvailable()
let finalDocs = similarDocs
if (rerankModelAvailable && similarDocs.length > 0) {
finalDocs = await rerankDocuments(query, similarDocs)
}
// 컨텍스트 문자열 생성
return finalDocs
.map(doc => `파일: ${doc.filename}\n내용: ${doc.content}`)
.join('\n\n---\n\n')
} catch (error) {
console.error('컨텍스트 검색 실패:', error)
return ''
}
}
리랭킹 모델
export async function rerankDocuments(
query: string,
documents: {id: number, filename: string, content: string, similarity: number}[]
): Promise<{id: number, filename: string, content: string, similarity: number}[]> {
try {
const modelInfo = await getRerankModelInfo()
if (!modelInfo) return documents
const { baseURL, apiKey, model } = modelInfo
const response = await fetch(baseURL + '/rerank', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model,
query,
documents: documents.map(doc => doc.content)
})
})
if (!response.ok) return documents
const data = await response.json()
// 리랭킹 결과에 따라 문서 재정렬
return data.results
.map((result: any) => ({
...documents[result.index],
similarity: result.relevance_score
}))
.sort((a: any, b: any) => b.similarity - a.similarity)
} catch (error) {
console.error('리랭킹 실패:', error)
return documents
}
}
실제 사용 예시
AI 설정
- 설정 페이지 접근: 앱 실행 후 설정 버튼 클릭
- AI 모델 구성:
- API 키 입력 (OpenAI, Anthropic 등)
- 로컬 모델 URL 설정 (Ollama, LM Studio)
- 모델명 지정
# Ollama 로컬 실행 예시
ollama serve
ollama pull llama3.1:8b
녹음 및 AI 대화
- 녹음 페이지: 스크린샷, 텍스트, 파일 등 다양한 형태로 정보 수집
- AI 어시스턴트: 수집된 정보를 바탕으로 AI와 대화
- 노트 생성: 대화 내용을 정리하여 마크다운 노트로 변환
RAG 활용
- 문서 인덱싱: 기존 마크다운 파일들을 벡터 데이터베이스에 저장
- 컨텍스트 검색: 질문 시 관련 문서 자동 검색
- 지능형 답변: 검색된 컨텍스트를 바탕으로 정확한 답변 생성
자동화 스크립트
빌드 자동화
#!/bin/bash
# build-notegen.sh
echo "🚀 NoteGen 빌드 시작..."
# 의존성 업데이트
echo "📦 의존성 설치..."
pnpm install
# 타입 체크
echo "🔍 타입 체크..."
pnpm run lint
# 빌드 실행
echo "🏗️ 빌드 실행..."
if [ "$1" == "desktop" ]; then
echo "🖥️ 데스크톱 앱 빌드..."
pnpm tauri build
else
echo "🌐 웹 앱 빌드..."
pnpm build
fi
echo "✅ 빌드 완료!"
개발 환경 설정
#!/bin/bash
# setup-dev.sh
echo "🛠️ NoteGen 개발 환경 설정..."
# Rust 설치 확인
if ! command -v rustc &> /dev/null; then
echo "📥 Rust 설치 중..."
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.zshrc
fi
# pnpm 설치 확인
if ! command -v pnpm &> /dev/null; then
echo "📥 pnpm 설치 중..."
npm install -g pnpm
fi
# Tauri CLI 설치
echo "📥 Tauri CLI 설치 중..."
cargo install tauri-cli
echo "✅ 개발 환경 설정 완료!"
echo "🎯 다음 명령어로 개발 서버를 시작하세요:"
echo " pnpm dev # 웹 버전"
echo " pnpm tauri dev # 데스크톱 버전"
zshrc Aliases
# ~/.zshrc에 추가
export NOTEGEN_DIR="$HOME/projects/note-gen"
# NoteGen 관련 alias
alias ng="cd $NOTEGEN_DIR"
alias ngdev="cd $NOTEGEN_DIR && pnpm dev"
alias ngtauri="cd $NOTEGEN_DIR && pnpm tauri dev"
alias ngbuild="cd $NOTEGEN_DIR && pnpm build"
alias ngdocker="cd $NOTEGEN_DIR && docker-compose up -d"
# 개발 도구 확인
alias checkdev="node --version && pnpm --version && rustc --version"
문제 해결
일반적인 오류
1. 의존성 충돌
# npm 대신 pnpm 사용
rm -rf node_modules package-lock.json
pnpm install
2. Rust 컴파일 오류
# Rust 툴체인 업데이트
rustup update
cargo clean
3. Tauri 빌드 실패
# Tauri 의존성 재설치
cargo install tauri-cli --force
pnpm tauri info
성능 최적화
청킹 파라미터 조정
// 큰 문서용
const chunks = chunkText(content, 2000, 400)
// 정밀한 검색용
const chunks = chunkText(content, 500, 100)
임베딩 배치 처리
async function batchProcessFiles(files: string[]) {
const batchSize = 5
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize)
await Promise.all(batch.map(processMarkdownFile))
// API 레이트 리밋 고려
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
결론
NoteGen은 AI 기반 마크다운 노트 앱의 새로운 패러다임을 제시합니다. Tauri와 Next.js의 조합으로 뛰어난 성능과 크로스플랫폼 호환성을 제공하며, 다양한 AI 모델과 RAG 기능으로 지능형 노트 작성을 가능하게 합니다.
주요 장점
- 경량화: 20MB 미만의 설치 파일
- 유연성: 다중 AI 프로바이더 지원
- 확장성: 플러그인 아키텍처
- 프라이버시: 로컬 데이터 저장
추천 사용 사례
- 연구 노트: 논문과 자료를 AI로 분석하고 정리
- 개발 문서: 코드와 문서를 연결한 지식 관리
- 학습 노트: AI 튜터와 함께하는 스마트 학습
- 회의록: 음성과 텍스트를 통합한 회의 기록
NoteGen은 단순한 노트 앱을 넘어 AI 시대의 지식 관리 도구로 자리잡을 잠재력을 가지고 있습니다. 오픈소스 프로젝트로서 커뮤니티의 기여를 통해 지속적으로 발전할 것으로 기대됩니다.