mirror of
https://github.com/asyouplz/SpeechNote.git
synced 2026-07-22 16:30:31 +00:00
- Set up project structure and architecture - Implement core transcription services with OpenAI Whisper API - Add comprehensive UI components and settings - Create documentation in Korean and English - Establish testing framework and examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
839 lines
No EOL
20 KiB
Markdown
839 lines
No EOL
20 KiB
Markdown
# 컴포넌트 가이드 (Component Guide)
|
|
|
|
## 목차
|
|
1. [개요](#개요)
|
|
2. [UI 컴포넌트](#ui-컴포넌트)
|
|
3. [서비스 컴포넌트](#서비스-컴포넌트)
|
|
4. [패턴 구현](#패턴-구현)
|
|
5. [통합 예제](#통합-예제)
|
|
6. [베스트 프랙티스](#베스트-프랙티스)
|
|
|
|
---
|
|
|
|
## 개요
|
|
|
|
이 문서는 Speech-to-Text 플러그인의 주요 컴포넌트들의 구조, 사용법, 통합 방법을 설명합니다.
|
|
|
|
### 컴포넌트 아키텍처
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[Main Plugin] --> B[Application Layer]
|
|
A --> C[Infrastructure Layer]
|
|
A --> D[UI Layer]
|
|
|
|
B --> B1[EditorService]
|
|
B --> B2[TextInsertionHandler]
|
|
B --> B3[EventManager]
|
|
B --> B4[StateManager]
|
|
|
|
C --> C1[WhisperService]
|
|
C --> C2[SettingsManager]
|
|
C --> C3[FileUploadManager]
|
|
C --> C4[Logger]
|
|
|
|
D --> D1[FilePickerModal]
|
|
D --> D2[DragDropZone]
|
|
D --> D3[ProgressIndicator]
|
|
D --> D4[RecentFiles]
|
|
```
|
|
|
|
---
|
|
|
|
## UI 컴포넌트
|
|
|
|
### DragDropZone
|
|
|
|
파일 드래그 앤 드롭 기능을 제공하는 컴포넌트입니다.
|
|
|
|
#### 기본 사용법
|
|
```typescript
|
|
import { DragDropZone } from './ui/components/DragDropZone';
|
|
|
|
const dropZone = new DragDropZone(container, {
|
|
onFileDrop: async (files) => {
|
|
for (const file of files) {
|
|
await processFile(file);
|
|
}
|
|
},
|
|
acceptedFormats: ['.m4a', '.mp3', '.wav'],
|
|
maxFileSize: 25 * 1024 * 1024
|
|
});
|
|
|
|
// 활성화/비활성화
|
|
dropZone.setEnabled(true);
|
|
|
|
// 정리
|
|
dropZone.destroy();
|
|
```
|
|
|
|
#### 주요 기능
|
|
- **파일 검증**: 형식과 크기 자동 검증
|
|
- **시각적 피드백**: 드래그 오버 시 하이라이트
|
|
- **다중 파일 지원**: 여러 파일 동시 처리
|
|
- **에러 처리**: 잘못된 파일 형식 알림
|
|
|
|
### FilePickerModal
|
|
|
|
파일 선택 모달 다이얼로그 컴포넌트입니다.
|
|
|
|
#### 기본 사용법
|
|
```typescript
|
|
import { FilePickerModal } from './ui/modals/FilePickerModal';
|
|
|
|
class AudioFilePickerModal extends FilePickerModal {
|
|
constructor(app: App, onSelect: (file: TFile) => void) {
|
|
super(app);
|
|
this.onSelect = onSelect;
|
|
}
|
|
|
|
protected getItems(): TFile[] {
|
|
return this.app.vault.getFiles()
|
|
.filter(f => this.isAudioFile(f));
|
|
}
|
|
|
|
protected onChooseItem(file: TFile): void {
|
|
this.onSelect(file);
|
|
}
|
|
}
|
|
|
|
// 사용
|
|
const modal = new AudioFilePickerModal(app, async (file) => {
|
|
await transcriptionService.transcribe(file);
|
|
});
|
|
modal.open();
|
|
```
|
|
|
|
#### 주요 기능
|
|
- **검색 기능**: 실시간 파일 검색
|
|
- **최근 파일**: 최근 사용한 파일 표시
|
|
- **파일 미리보기**: 파일 정보 표시
|
|
- **키보드 단축키**: Enter/Esc 지원
|
|
|
|
### ProgressIndicator
|
|
|
|
진행 상황을 표시하는 컴포넌트입니다.
|
|
|
|
#### 기본 사용법
|
|
```typescript
|
|
import { ProgressIndicator } from './ui/components/ProgressIndicator';
|
|
|
|
const progress = new ProgressIndicator();
|
|
|
|
// 진행 상황 업데이트
|
|
progress.update({
|
|
percentage: 50,
|
|
message: '파일 처리 중...',
|
|
status: 'processing'
|
|
});
|
|
|
|
// 완료
|
|
progress.complete('변환 완료!');
|
|
|
|
// 에러
|
|
progress.error('변환 실패');
|
|
|
|
// 숨기기
|
|
progress.hide();
|
|
```
|
|
|
|
#### 상태 타입
|
|
```typescript
|
|
type ProgressStatus =
|
|
| 'idle' // 대기
|
|
| 'preparing' // 준비 중
|
|
| 'uploading' // 업로드 중
|
|
| 'processing' // 처리 중
|
|
| 'completed' // 완료
|
|
| 'error'; // 에러
|
|
```
|
|
|
|
### RecentFiles
|
|
|
|
최근 사용한 파일 목록을 관리하는 컴포넌트입니다.
|
|
|
|
#### 기본 사용법
|
|
```typescript
|
|
import { RecentFiles } from './ui/components/RecentFiles';
|
|
|
|
const recentFiles = new RecentFiles(maxItems = 10);
|
|
|
|
// 파일 추가
|
|
recentFiles.add(file);
|
|
|
|
// 목록 가져오기
|
|
const files = recentFiles.getAll();
|
|
|
|
// 특정 파일 제거
|
|
recentFiles.remove(file.path);
|
|
|
|
// 전체 초기화
|
|
recentFiles.clear();
|
|
```
|
|
|
|
---
|
|
|
|
## 서비스 컴포넌트
|
|
|
|
### WhisperService
|
|
|
|
OpenAI Whisper API와 통신하는 핵심 서비스입니다.
|
|
|
|
#### 초기화 및 설정
|
|
```typescript
|
|
import { WhisperService } from './infrastructure/api/WhisperService';
|
|
import { Logger } from './infrastructure/logging/Logger';
|
|
|
|
const logger = new Logger('WhisperService');
|
|
const whisperService = new WhisperService(apiKey, logger);
|
|
```
|
|
|
|
#### 음성 변환
|
|
```typescript
|
|
// 기본 변환
|
|
const response = await whisperService.transcribe(audioBuffer);
|
|
|
|
// 옵션과 함께 변환
|
|
const response = await whisperService.transcribe(audioBuffer, {
|
|
language: 'ko',
|
|
prompt: '이전 대화 컨텍스트...',
|
|
temperature: 0.2,
|
|
responseFormat: 'verbose_json'
|
|
});
|
|
|
|
// 세그먼트 정보 포함
|
|
if (response.segments) {
|
|
response.segments.forEach(segment => {
|
|
console.log(`[${segment.start}-${segment.end}] ${segment.text}`);
|
|
});
|
|
}
|
|
```
|
|
|
|
#### 에러 처리
|
|
```typescript
|
|
try {
|
|
const response = await whisperService.transcribe(audioBuffer);
|
|
} catch (error) {
|
|
if (error instanceof AuthenticationError) {
|
|
// API 키 문제
|
|
await settingsManager.setApiKey('');
|
|
} else if (error instanceof RateLimitError) {
|
|
// 재시도 로직
|
|
setTimeout(() => retry(), error.retryAfter * 1000);
|
|
} else if (error instanceof FileTooLargeError) {
|
|
// 파일 압축 필요
|
|
const compressed = await compressAudio(audioBuffer);
|
|
await whisperService.transcribe(compressed);
|
|
}
|
|
}
|
|
```
|
|
|
|
### SettingsManager
|
|
|
|
플러그인 설정을 관리하는 서비스입니다.
|
|
|
|
#### 설정 로드 및 저장
|
|
```typescript
|
|
import { SettingsManager } from './infrastructure/storage/SettingsManager';
|
|
|
|
const settingsManager = new SettingsManager(plugin, logger);
|
|
|
|
// 설정 로드
|
|
const settings = await settingsManager.load();
|
|
|
|
// 개별 설정 값 가져오기
|
|
const language = settingsManager.get('language');
|
|
const autoInsert = settingsManager.get('autoInsert');
|
|
|
|
// 설정 업데이트
|
|
await settingsManager.set('language', 'ko');
|
|
await settingsManager.set('temperature', 0.3);
|
|
|
|
// 전체 설정 저장
|
|
await settingsManager.save({
|
|
...settings,
|
|
language: 'en'
|
|
});
|
|
```
|
|
|
|
#### API 키 관리
|
|
```typescript
|
|
// API 키 설정 (자동 암호화)
|
|
const success = await settingsManager.setApiKey('sk-...');
|
|
|
|
// API 키 가져오기 (복호화됨)
|
|
const apiKey = settingsManager.getApiKey();
|
|
|
|
// 마스킹된 API 키 표시
|
|
const masked = settingsManager.getMaskedApiKey();
|
|
console.log(masked); // "sk-XXXXX...XXXX"
|
|
```
|
|
|
|
### FileUploadManager
|
|
|
|
대용량 파일 처리 및 업로드를 관리합니다.
|
|
|
|
#### 파일 처리
|
|
```typescript
|
|
import { FileUploadManager } from './infrastructure/api/FileUploadManager';
|
|
|
|
const uploadManager = new FileUploadManager(vault, logger);
|
|
|
|
// 진행 상황 콜백과 함께 파일 처리
|
|
const processed = await uploadManager.prepareAudioFile(file, (progress) => {
|
|
console.log(`${progress.percentage}% - ${progress.message}`);
|
|
progressIndicator.update(progress);
|
|
});
|
|
|
|
console.log(`원본: ${processed.originalSize}, 처리 후: ${processed.processedSize}`);
|
|
```
|
|
|
|
#### 청크 업로드
|
|
```typescript
|
|
// 대용량 파일을 청크로 나누어 업로드
|
|
for await (const chunk of uploadManager.uploadInChunks(buffer)) {
|
|
await sendChunkToServer(chunk);
|
|
}
|
|
|
|
// 취소 기능
|
|
uploadManager.cancel();
|
|
```
|
|
|
|
### EditorService
|
|
|
|
Obsidian 에디터와의 통합을 담당합니다.
|
|
|
|
#### 텍스트 삽입
|
|
```typescript
|
|
import { EditorService } from './application/EditorService';
|
|
|
|
const editorService = new EditorService(app, eventManager, logger);
|
|
|
|
// 커서 위치에 삽입
|
|
await editorService.insertAtCursor("변환된 텍스트");
|
|
|
|
// 선택 영역 대체
|
|
await editorService.replaceSelection("새로운 텍스트");
|
|
|
|
// 문서 끝에 추가
|
|
await editorService.appendToDocument("\n\n## 추가 내용");
|
|
|
|
// 특정 위치에 삽입
|
|
const position = { line: 10, ch: 0 };
|
|
await editorService.insertAtPosition("삽입할 텍스트", position);
|
|
```
|
|
|
|
#### Undo/Redo 지원
|
|
```typescript
|
|
// 작업 실행 (자동으로 히스토리에 기록)
|
|
await editorService.insertAtCursor("텍스트", true);
|
|
|
|
// Undo
|
|
await editorService.undo();
|
|
|
|
// Redo
|
|
await editorService.redo();
|
|
|
|
// 히스토리 초기화
|
|
editorService.clearHistory();
|
|
```
|
|
|
|
### TextInsertionHandler
|
|
|
|
텍스트 포맷팅과 삽입을 처리합니다.
|
|
|
|
#### 포맷팅 옵션
|
|
```typescript
|
|
import { TextInsertionHandler } from './application/TextInsertionHandler';
|
|
|
|
const insertionHandler = new TextInsertionHandler(
|
|
editorService,
|
|
eventManager,
|
|
logger
|
|
);
|
|
|
|
// 인용구 형식으로 삽입
|
|
await insertionHandler.insertText("변환된 텍스트", {
|
|
mode: 'cursor',
|
|
format: 'quote',
|
|
quoteAuthor: 'Speaker Name'
|
|
});
|
|
|
|
// 코드 블록으로 삽입
|
|
await insertionHandler.insertText("코드 내용", {
|
|
mode: 'cursor',
|
|
format: 'code',
|
|
codeLanguage: 'javascript'
|
|
});
|
|
|
|
// 타임스탬프와 함께 삽입
|
|
await insertionHandler.insertText("텍스트", {
|
|
mode: 'cursor',
|
|
format: 'plain',
|
|
addTimestamp: true,
|
|
timestampFormat: 'YYYY-MM-DD HH:mm:ss'
|
|
});
|
|
```
|
|
|
|
#### 템플릿 사용
|
|
```typescript
|
|
// 템플릿 적용
|
|
await insertionHandler.insertText("내용", {
|
|
mode: 'cursor',
|
|
format: 'plain',
|
|
template: '## {{date}} 회의록\n\n{{content}}\n\n---\n작성: {{time}}'
|
|
});
|
|
```
|
|
|
|
### EventManager
|
|
|
|
애플리케이션 전체의 이벤트를 관리합니다.
|
|
|
|
#### 이벤트 구독
|
|
```typescript
|
|
import { EventManager } from './application/EventManager';
|
|
|
|
const eventManager = new EventManager(logger);
|
|
|
|
// 이벤트 리스너 등록
|
|
const unsubscribe = eventManager.on('transcription:complete', (data) => {
|
|
console.log(`변환 완료: ${data.text.substring(0, 50)}...`);
|
|
|
|
if (data.autoInsert) {
|
|
insertionHandler.insertText(data.text, insertOptions);
|
|
}
|
|
});
|
|
|
|
// 일회성 리스너
|
|
eventManager.once('file:selected', (data) => {
|
|
console.log(`파일 선택됨: ${data.file.path}`);
|
|
});
|
|
|
|
// 구독 해제
|
|
unsubscribe();
|
|
```
|
|
|
|
#### 고급 기능
|
|
```typescript
|
|
// 이벤트 디바운싱 (연속 호출 방지)
|
|
eventManager.debounce('settings:changed', 500, (data) => {
|
|
saveSettings(data);
|
|
});
|
|
|
|
// 이벤트 쓰로틀링 (호출 빈도 제한)
|
|
eventManager.throttle('transcription:progress', 100, (data) => {
|
|
updateProgressBar(data.progress);
|
|
});
|
|
|
|
// 이벤트 체인
|
|
eventManager.chain(
|
|
'transcription:complete',
|
|
'editor:text-inserted',
|
|
(data) => ({ text: data.text, position: 'cursor' })
|
|
);
|
|
|
|
// 이벤트 필터링
|
|
eventManager.filter(
|
|
'file:validated',
|
|
(data) => data.valid === true,
|
|
(data) => processValidFile(data.file)
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 패턴 구현
|
|
|
|
### Singleton Pattern
|
|
|
|
전역적으로 단일 인스턴스를 보장합니다.
|
|
|
|
```typescript
|
|
import { SingletonDecorator } from './patterns/Singleton';
|
|
|
|
@SingletonDecorator
|
|
class GlobalStateManager {
|
|
private state: AppState;
|
|
|
|
getState(): AppState {
|
|
return this.state;
|
|
}
|
|
}
|
|
|
|
// 어디서든 동일한 인스턴스
|
|
const manager1 = new GlobalStateManager();
|
|
const manager2 = new GlobalStateManager();
|
|
console.log(manager1 === manager2); // true
|
|
```
|
|
|
|
### Observer Pattern
|
|
|
|
이벤트 기반 통신을 구현합니다.
|
|
|
|
```typescript
|
|
import { EventEmitter } from './patterns/Observer';
|
|
|
|
interface CustomEvents {
|
|
'data:updated': { id: string; value: any };
|
|
'error:occurred': { error: Error };
|
|
}
|
|
|
|
class DataService extends EventEmitter<CustomEvents> {
|
|
updateData(id: string, value: any) {
|
|
// 데이터 업데이트
|
|
this.emit('data:updated', { id, value });
|
|
}
|
|
}
|
|
```
|
|
|
|
### Factory Pattern
|
|
|
|
객체 생성을 추상화합니다.
|
|
|
|
```typescript
|
|
import { ServiceFactory } from './patterns/Factory';
|
|
|
|
const factory = new ServiceFactory();
|
|
|
|
// 서비스 등록
|
|
factory.register('whisper', () => new WhisperService(apiKey, logger));
|
|
factory.register('settings', () => new SettingsManager(plugin));
|
|
|
|
// 서비스 생성
|
|
const whisperService = factory.create('whisper');
|
|
const settingsManager = factory.create('settings');
|
|
```
|
|
|
|
---
|
|
|
|
## 통합 예제
|
|
|
|
### 완전한 음성 변환 플로우
|
|
|
|
```typescript
|
|
class SpeechToTextWorkflow {
|
|
constructor(
|
|
private whisperService: WhisperService,
|
|
private uploadManager: FileUploadManager,
|
|
private editorService: EditorService,
|
|
private insertionHandler: TextInsertionHandler,
|
|
private eventManager: EventManager,
|
|
private logger: Logger
|
|
) {}
|
|
|
|
async processAudioFile(file: TFile): Promise<void> {
|
|
try {
|
|
// 1. 시작 이벤트 발생
|
|
this.eventManager.emit('transcription:start', {
|
|
fileName: file.name,
|
|
fileSize: file.stat.size
|
|
});
|
|
|
|
// 2. 파일 준비 및 검증
|
|
const processed = await this.uploadManager.prepareAudioFile(
|
|
file,
|
|
(progress) => {
|
|
this.eventManager.emit('transcription:progress', {
|
|
progress: progress.percentage * 0.3, // 0-30%
|
|
message: progress.message
|
|
});
|
|
}
|
|
);
|
|
|
|
// 3. API 호출
|
|
this.eventManager.emit('transcription:progress', {
|
|
progress: 30,
|
|
message: 'Transcribing audio...'
|
|
});
|
|
|
|
const response = await this.whisperService.transcribe(
|
|
processed.buffer,
|
|
{
|
|
language: this.settings.language,
|
|
responseFormat: 'verbose_json'
|
|
}
|
|
);
|
|
|
|
// 4. 텍스트 포맷팅 및 삽입
|
|
this.eventManager.emit('transcription:progress', {
|
|
progress: 90,
|
|
message: 'Formatting text...'
|
|
});
|
|
|
|
await this.insertionHandler.insertText(response.text, {
|
|
mode: this.settings.insertPosition,
|
|
format: 'plain',
|
|
addTimestamp: this.settings.timestampFormat !== 'none',
|
|
language: response.language
|
|
});
|
|
|
|
// 5. 완료 이벤트
|
|
this.eventManager.emit('transcription:complete', {
|
|
text: response.text,
|
|
duration: processed.metadata.duration || 0
|
|
});
|
|
|
|
// 6. 성공 알림
|
|
new Notice(`Transcription complete! (${response.language})`);
|
|
|
|
} catch (error) {
|
|
// 에러 처리
|
|
this.eventManager.emit('transcription:error', {
|
|
error: error as Error,
|
|
fileName: file.name
|
|
});
|
|
|
|
this.logger.error('Transcription failed', error as Error);
|
|
new Notice(`Failed: ${(error as Error).message}`);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### 설정 UI 통합
|
|
|
|
```typescript
|
|
class SettingsTab extends PluginSettingTab {
|
|
constructor(
|
|
app: App,
|
|
plugin: Plugin,
|
|
private settingsManager: SettingsManager,
|
|
private whisperService: WhisperService
|
|
) {
|
|
super(app, plugin);
|
|
}
|
|
|
|
display(): void {
|
|
const { containerEl } = this;
|
|
containerEl.empty();
|
|
|
|
// API 키 설정
|
|
new Setting(containerEl)
|
|
.setName('OpenAI API Key')
|
|
.setDesc('Your OpenAI API key for Whisper')
|
|
.addText(text => text
|
|
.setPlaceholder('sk-...')
|
|
.setValue(this.settingsManager.getMaskedApiKey())
|
|
.onChange(async (value) => {
|
|
if (value && !value.includes('*')) {
|
|
const isValid = await this.whisperService.validateApiKey(value);
|
|
if (isValid) {
|
|
await this.settingsManager.setApiKey(value);
|
|
new Notice('API key validated and saved');
|
|
} else {
|
|
new Notice('Invalid API key');
|
|
}
|
|
}
|
|
})
|
|
);
|
|
|
|
// 언어 설정
|
|
new Setting(containerEl)
|
|
.setName('Language')
|
|
.setDesc('Transcription language')
|
|
.addDropdown(dropdown => dropdown
|
|
.addOptions({
|
|
'auto': 'Auto-detect',
|
|
'ko': 'Korean',
|
|
'en': 'English',
|
|
'ja': 'Japanese',
|
|
'zh': 'Chinese'
|
|
})
|
|
.setValue(this.settingsManager.get('language'))
|
|
.onChange(async (value) => {
|
|
await this.settingsManager.set('language', value);
|
|
})
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 베스트 프랙티스
|
|
|
|
### 1. 에러 처리
|
|
|
|
항상 try-catch로 에러를 처리하고 사용자에게 적절한 피드백을 제공하세요.
|
|
|
|
```typescript
|
|
try {
|
|
await riskyOperation();
|
|
} catch (error) {
|
|
// 로깅
|
|
logger.error('Operation failed', error);
|
|
|
|
// 사용자 알림
|
|
new Notice(errorHandler.getUserMessage(error));
|
|
|
|
// 이벤트 발생
|
|
eventManager.emit('error:occurred', { error });
|
|
|
|
// 복구 가능한 경우 재시도
|
|
if (errorHandler.isRetryable(error)) {
|
|
await retry(riskyOperation);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. 리소스 정리
|
|
|
|
컴포넌트 사용 후 항상 정리하세요.
|
|
|
|
```typescript
|
|
class MyComponent {
|
|
private subscriptions: Unsubscribe[] = [];
|
|
|
|
initialize() {
|
|
// 이벤트 구독
|
|
this.subscriptions.push(
|
|
eventManager.on('event', this.handleEvent)
|
|
);
|
|
}
|
|
|
|
destroy() {
|
|
// 모든 구독 해제
|
|
this.subscriptions.forEach(unsub => unsub());
|
|
|
|
// 리소스 정리
|
|
this.uploadManager?.cleanup();
|
|
this.whisperService?.cancel();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. 비동기 작업 관리
|
|
|
|
```typescript
|
|
class AsyncOperationManager {
|
|
private operations = new Set<Promise<any>>();
|
|
|
|
async execute<T>(operation: () => Promise<T>): Promise<T> {
|
|
const promise = operation();
|
|
this.operations.add(promise);
|
|
|
|
try {
|
|
const result = await promise;
|
|
return result;
|
|
} finally {
|
|
this.operations.delete(promise);
|
|
}
|
|
}
|
|
|
|
async cancelAll() {
|
|
await Promise.allSettled(this.operations);
|
|
this.operations.clear();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. 타입 안전성
|
|
|
|
TypeScript의 타입 시스템을 최대한 활용하세요.
|
|
|
|
```typescript
|
|
// 타입 가드 사용
|
|
function isWhisperResponse(obj: any): obj is WhisperResponse {
|
|
return obj && typeof obj.text === 'string';
|
|
}
|
|
|
|
// 제네릭 활용
|
|
function createService<T extends IService>(
|
|
ServiceClass: new (...args: any[]) => T,
|
|
...args: any[]
|
|
): T {
|
|
return new ServiceClass(...args);
|
|
}
|
|
|
|
// 유틸리티 타입
|
|
type DeepPartial<T> = {
|
|
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
};
|
|
```
|
|
|
|
### 5. 성능 최적화
|
|
|
|
```typescript
|
|
// 디바운싱
|
|
const debouncedSave = debounce(saveSettings, 500);
|
|
|
|
// 메모이제이션
|
|
const memoizedExpensiveOperation = memoize(expensiveOperation);
|
|
|
|
// 레이지 로딩
|
|
const LazyService = lazy(() => import('./HeavyService'));
|
|
```
|
|
|
|
---
|
|
|
|
## 컴포넌트 생명주기
|
|
|
|
### 초기화 순서
|
|
|
|
1. **Logger 초기화**: 로깅 시스템 설정
|
|
2. **EventManager 생성**: 이벤트 시스템 준비
|
|
3. **Settings 로드**: 설정 불러오기
|
|
4. **Services 초기화**: 각 서비스 인스턴스 생성
|
|
5. **UI 컴포넌트 생성**: UI 요소 초기화
|
|
6. **이벤트 리스너 등록**: 이벤트 구독 설정
|
|
|
|
### 종료 순서
|
|
|
|
1. **진행 중인 작업 취소**: 모든 비동기 작업 중단
|
|
2. **이벤트 리스너 해제**: 구독 정리
|
|
3. **UI 컴포넌트 정리**: DOM 요소 제거
|
|
4. **서비스 정리**: 리소스 해제
|
|
5. **설정 저장**: 마지막 상태 저장
|
|
6. **Logger 종료**: 로그 플러시
|
|
|
|
---
|
|
|
|
## 디버깅 도구
|
|
|
|
### 개발 모드 활성화
|
|
|
|
```typescript
|
|
// 개발 환경에서만 디버그 모드 활성화
|
|
if (process.env.NODE_ENV === 'development') {
|
|
eventManager.setDebugMode(true);
|
|
logger.setLevel(LogLevel.DEBUG);
|
|
|
|
// 전역 디버그 객체
|
|
window.speechToTextDebug = {
|
|
services: {
|
|
whisper: whisperService,
|
|
settings: settingsManager,
|
|
editor: editorService
|
|
},
|
|
eventManager,
|
|
logger
|
|
};
|
|
}
|
|
```
|
|
|
|
### 이벤트 모니터링
|
|
|
|
```typescript
|
|
// 모든 이벤트 로깅
|
|
const events = [
|
|
'transcription:start',
|
|
'transcription:progress',
|
|
'transcription:complete',
|
|
'transcription:error'
|
|
];
|
|
|
|
events.forEach(event => {
|
|
eventManager.on(event as any, (data) => {
|
|
console.log(`[Event] ${event}:`, data);
|
|
});
|
|
});
|
|
|
|
// 통계 확인
|
|
console.log('Event Stats:', eventManager.getStats());
|
|
console.log('Event History:', eventManager.getHistory());
|
|
```
|
|
|
|
---
|
|
|
|
*최종 업데이트: 2025-08-22*
|
|
*버전: 1.0.0* |