@aituber-onair/manneri 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +609 -0
- package/README.md +600 -0
- package/dist/analyzers/KeywordExtractor.d.ts +48 -0
- package/dist/analyzers/KeywordExtractor.js +253 -0
- package/dist/analyzers/KeywordExtractor.js.map +1 -0
- package/dist/analyzers/PatternDetector.d.ts +38 -0
- package/dist/analyzers/PatternDetector.js +244 -0
- package/dist/analyzers/PatternDetector.js.map +1 -0
- package/dist/analyzers/SimilarityAnalyzer.d.ts +23 -0
- package/dist/analyzers/SimilarityAnalyzer.js +153 -0
- package/dist/analyzers/SimilarityAnalyzer.js.map +1 -0
- package/dist/config/defaultPrompts.d.ts +5 -0
- package/dist/config/defaultPrompts.js +22 -0
- package/dist/config/defaultPrompts.js.map +1 -0
- package/dist/core/ConversationAnalyzer.d.ts +51 -0
- package/dist/core/ConversationAnalyzer.js +213 -0
- package/dist/core/ConversationAnalyzer.js.map +1 -0
- package/dist/core/ManneriDetector.d.ts +64 -0
- package/dist/core/ManneriDetector.js +251 -0
- package/dist/core/ManneriDetector.js.map +1 -0
- package/dist/generators/PromptGenerator.d.ts +15 -0
- package/dist/generators/PromptGenerator.js +45 -0
- package/dist/generators/PromptGenerator.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/persistence/LocalStoragePersistenceProvider.d.ts +45 -0
- package/dist/persistence/LocalStoragePersistenceProvider.js +102 -0
- package/dist/persistence/LocalStoragePersistenceProvider.js.map +1 -0
- package/dist/persistence/index.d.ts +5 -0
- package/dist/persistence/index.js +5 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/types/index.d.ts +114 -0
- package/dist/types/index.js +28 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/persistence.d.ts +78 -0
- package/dist/types/persistence.js +2 -0
- package/dist/types/persistence.js.map +1 -0
- package/dist/types/prompts.d.ts +22 -0
- package/dist/types/prompts.js +36 -0
- package/dist/types/prompts.js.map +1 -0
- package/dist/utils/browserUtils.d.ts +20 -0
- package/dist/utils/browserUtils.js +206 -0
- package/dist/utils/browserUtils.js.map +1 -0
- package/dist/utils/textUtils.d.ts +10 -0
- package/dist/utils/textUtils.js +269 -0
- package/dist/utils/textUtils.js.map +1 -0
- package/package.json +50 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
# @aituber-onair/manneri
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
**Manneri** は、AIチャットボットの会話における繰り返しパターンを検出し、話題変更プロンプトを提供してより魅力的な会話を実現するシンプルなJavaScriptライブラリです。
|
|
6
|
+
|
|
7
|
+
## 特徴
|
|
8
|
+
|
|
9
|
+
- 🔍 **会話の類似度分析**: テキストの類似度を計算して繰り返しを検出
|
|
10
|
+
- 📊 **パターン検出**: 会話の構造的なパターンを識別
|
|
11
|
+
- 🎯 **キーワード分析**: 頻出語彙と話題の偏りを検出
|
|
12
|
+
- 💡 **自動プロンプト生成**: 話題変更のための適切なプロンプトを生成
|
|
13
|
+
- 🌐 **フロントエンド専用**: ブラウザ環境での軽量動作
|
|
14
|
+
- 🌍 **多言語対応**: 日本語・英語の組み込みサポート、カスタムプロンプトで任意の言語に対応可能
|
|
15
|
+
- 🎨 **カスタマイズ可能なプロンプト**: 任意の言語で介入メッセージや推奨事項を設定可能
|
|
16
|
+
- 🇯🇵 **日本語対応**: 日本語テキストの適切な処理(ひらがな、カタカナ、漢字)
|
|
17
|
+
- 💾 **柔軟な永続化**: 複数のストレージバックエンドをサポートする設定可能なデータ永続化
|
|
18
|
+
|
|
19
|
+
## インストール
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @aituber-onair/manneri
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 基本的な使用方法
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { ManneriDetector, LocalStoragePersistenceProvider } from '@aituber-onair/manneri';
|
|
29
|
+
|
|
30
|
+
// デフォルト設定でManneriDetectorを作成
|
|
31
|
+
const detector = new ManneriDetector();
|
|
32
|
+
|
|
33
|
+
// メッセージ配列を定義
|
|
34
|
+
const messages = [
|
|
35
|
+
{ role: 'user', content: 'こんにちは' },
|
|
36
|
+
{ role: 'assistant', content: 'こんにちは!今日はどのようなご用件でしょうか?' },
|
|
37
|
+
{ role: 'user', content: 'こんにちは' },
|
|
38
|
+
{ role: 'assistant', content: 'こんにちは!何かお手伝いできることはありますか?' }
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// マンネリ化を検出
|
|
42
|
+
if (detector.detectManneri(messages)) {
|
|
43
|
+
console.log('会話の繰り返しが検出されました');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 介入が必要かチェック(クールダウン考慮)
|
|
47
|
+
if (detector.shouldIntervene(messages)) {
|
|
48
|
+
// 話題変更プロンプトを生成
|
|
49
|
+
const prompt = detector.generateDiversificationPrompt(messages);
|
|
50
|
+
console.log('提案プロンプト:', prompt.content);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 設定オプション
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const detector = new ManneriDetector({
|
|
58
|
+
similarityThreshold: 0.75, // 類似度閾値 (0-1)
|
|
59
|
+
repetitionLimit: 3, // 繰り返し検出回数
|
|
60
|
+
lookbackWindow: 10, // 分析対象のメッセージ数
|
|
61
|
+
interventionCooldown: 300000, // 介入間隔(ミリ秒)
|
|
62
|
+
minMessageLength: 10, // 分析対象の最小文字数
|
|
63
|
+
excludeKeywords: ['はい', 'いいえ'], // 除外キーワード
|
|
64
|
+
enableTopicTracking: true, // 話題追跡を有効化
|
|
65
|
+
enableKeywordAnalysis: true, // キーワード分析を有効化
|
|
66
|
+
debugMode: false, // デバッグモード
|
|
67
|
+
language: 'ja', // プロンプト言語 ('ja' | 'en' | カスタム)
|
|
68
|
+
customPrompts: { // カスタム介入プロンプト(オプション)
|
|
69
|
+
ja: {
|
|
70
|
+
intervention: [
|
|
71
|
+
'話題を変えて、新しい内容について話しましょう。',
|
|
72
|
+
'別の角度から話題を展開してみませんか?',
|
|
73
|
+
'違うテーマで会話を続けてみましょう。'
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}, {
|
|
78
|
+
// オプション: 永続化プロバイダーを設定
|
|
79
|
+
persistenceProvider: new LocalStoragePersistenceProvider({
|
|
80
|
+
storageKey: 'my_manneri_data',
|
|
81
|
+
version: '1.0.0'
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## AITuberOnAirCoreとの統合
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { ManneriDetector } from '@aituber-onair/manneri';
|
|
90
|
+
|
|
91
|
+
const manneriDetector = new ManneriDetector({
|
|
92
|
+
similarityThreshold: 0.8,
|
|
93
|
+
repetitionLimit: 3,
|
|
94
|
+
interventionCooldown: 300000
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// AITuberOnAirCoreのイベントリスナーで統合
|
|
98
|
+
core.on('beforeAIRequest', (requestData) => {
|
|
99
|
+
const chatHistory = core.getChatHistory();
|
|
100
|
+
|
|
101
|
+
if (manneriDetector.shouldIntervene(chatHistory)) {
|
|
102
|
+
const diversificationPrompt = manneriDetector.generateDiversificationPrompt(chatHistory);
|
|
103
|
+
|
|
104
|
+
// システムプロンプトとして話題変更指示を追加
|
|
105
|
+
requestData.messages.unshift({
|
|
106
|
+
role: 'system',
|
|
107
|
+
content: diversificationPrompt.content
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log('話題変更プロンプトを適用:', diversificationPrompt.type);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## イベント処理
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// 類似度計算イベント
|
|
119
|
+
detector.on('similarity_calculated', (data) => {
|
|
120
|
+
console.log(`類似度: ${data.score}, 閾値: ${data.threshold}`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// パターン検出イベント
|
|
124
|
+
detector.on('pattern_detected', (result) => {
|
|
125
|
+
console.log('検出されたパターン:', result.patterns);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// 介入実行イベント
|
|
129
|
+
detector.on('intervention_triggered', (prompt) => {
|
|
130
|
+
console.log('介入実行:', prompt.content);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// 設定更新イベント
|
|
134
|
+
detector.on('config_updated', (newConfig) => {
|
|
135
|
+
console.log('設定更新:', newConfig);
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 詳細分析
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// 詳細な会話分析を実行
|
|
143
|
+
const analysis = detector.analyzeConversation(messages);
|
|
144
|
+
|
|
145
|
+
console.log('分析結果:', {
|
|
146
|
+
similarity: analysis.similarity,
|
|
147
|
+
topics: analysis.topics,
|
|
148
|
+
patterns: analysis.patterns,
|
|
149
|
+
shouldIntervene: analysis.shouldIntervene,
|
|
150
|
+
reason: analysis.interventionReason
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// 統計情報を取得
|
|
154
|
+
const stats = detector.getStatistics();
|
|
155
|
+
console.log('統計:', {
|
|
156
|
+
totalInterventions: stats.totalInterventions,
|
|
157
|
+
averageInterval: stats.averageInterventionInterval,
|
|
158
|
+
thresholds: stats.configuredThresholds
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## 多言語対応
|
|
163
|
+
|
|
164
|
+
### 言語設定
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// 英語プロンプトを使用
|
|
168
|
+
const detectorEn = new ManneriDetector({
|
|
169
|
+
language: 'en'
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// 日本語プロンプトを使用(デフォルト)
|
|
173
|
+
const detectorJa = new ManneriDetector({
|
|
174
|
+
language: 'ja'
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// 中国語対応
|
|
178
|
+
const detectorChinese = new ManneriDetector({
|
|
179
|
+
language: 'zh',
|
|
180
|
+
customPrompts: {
|
|
181
|
+
zh: {
|
|
182
|
+
intervention: [
|
|
183
|
+
'让我们换个话题,聊些新的内容。',
|
|
184
|
+
'我们来探讨一个不同的主题。',
|
|
185
|
+
'要不要讨论别的事情?'
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// 韓国語対応
|
|
192
|
+
const detectorKorean = new ManneriDetector({
|
|
193
|
+
language: 'ko',
|
|
194
|
+
customPrompts: {
|
|
195
|
+
ko: {
|
|
196
|
+
intervention: [
|
|
197
|
+
'새로운 주제로 변경해 주세요.',
|
|
198
|
+
'다른 이야기를 해봅시다.',
|
|
199
|
+
'화제를 바꿔볼까요?'
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// フランス語対応の例
|
|
206
|
+
const detectorFrench = new ManneriDetector({
|
|
207
|
+
language: 'fr',
|
|
208
|
+
customPrompts: {
|
|
209
|
+
fr: {
|
|
210
|
+
intervention: [
|
|
211
|
+
'Changeons de sujet et parlons de quelque chose de nouveau.',
|
|
212
|
+
'Explorons un sujet différent.',
|
|
213
|
+
'Que diriez-vous de discuter d\'autre chose?'
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 組み込み言語サポート
|
|
221
|
+
|
|
222
|
+
Manneriには以下の言語のプロンプトが組み込まれています:
|
|
223
|
+
- **日本語** (`'ja'`) - デフォルト言語
|
|
224
|
+
- **英語** (`'en'`) - 完全なプロンプトカバレッジ
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { DEFAULT_PROMPTS } from '@aituber-onair/manneri';
|
|
228
|
+
|
|
229
|
+
// 組み込み言語を確認
|
|
230
|
+
console.log(Object.keys(DEFAULT_PROMPTS)); // ['ja', 'en']
|
|
231
|
+
|
|
232
|
+
// 特定言語のプロンプトにアクセス
|
|
233
|
+
const japanesePrompts = DEFAULT_PROMPTS.ja;
|
|
234
|
+
console.log(japanesePrompts.intervention);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 任意の言語への対応
|
|
238
|
+
|
|
239
|
+
**カスタムプロンプトを提供することで、任意の言語に対応可能**です。ライブラリは任意の言語コードを受け入れ、カスタムプロンプトを使用します:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// スペイン語対応の例
|
|
243
|
+
const detectorSpanish = new ManneriDetector({
|
|
244
|
+
language: 'es',
|
|
245
|
+
customPrompts: {
|
|
246
|
+
es: {
|
|
247
|
+
intervention: [
|
|
248
|
+
'Cambiemos de tema y hablemos de algo nuevo.',
|
|
249
|
+
'Exploremos un tema diferente.',
|
|
250
|
+
'¿Qué tal si discutimos otra cosa?'
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// 既存言語の拡張
|
|
257
|
+
import { overridePrompts, DEFAULT_PROMPTS } from '@aituber-onair/manneri';
|
|
258
|
+
|
|
259
|
+
const multilingualPrompts = overridePrompts(DEFAULT_PROMPTS, {
|
|
260
|
+
zh: { intervention: ['让我们换个话题,聊些新的内容。'] },
|
|
261
|
+
ko: { intervention: ['새로운 주제로 변경해 주세요。'] },
|
|
262
|
+
fr: { intervention: ['Changeons de sujet.'] },
|
|
263
|
+
de: { intervention: ['Lassen Sie uns das Thema wechseln.'] },
|
|
264
|
+
// 必要な言語を追加
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 言語非依存設計
|
|
269
|
+
|
|
270
|
+
ライブラリはコード変更なしで**任意の言語**で動作するように設計されています:
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// 簡単な言語切り替え
|
|
274
|
+
const createDetectorForLanguage = (lang: string, prompts: any) => {
|
|
275
|
+
return new ManneriDetector({
|
|
276
|
+
language: lang,
|
|
277
|
+
customPrompts: { [lang]: prompts }
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// 同一アプリケーションで複数言語サポート
|
|
282
|
+
const detectors = {
|
|
283
|
+
japanese: createDetectorForLanguage('ja', japanesePrompts),
|
|
284
|
+
english: createDetectorForLanguage('en', englishPrompts),
|
|
285
|
+
chinese: createDetectorForLanguage('zh', chinesePrompts),
|
|
286
|
+
korean: createDetectorForLanguage('ko', koreanPrompts),
|
|
287
|
+
arabic: createDetectorForLanguage('ar', arabicPrompts),
|
|
288
|
+
// 任意の言語を追加
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// 動的言語検出
|
|
292
|
+
const getUserLanguage = () => navigator.language.split('-')[0];
|
|
293
|
+
const userDetector = detectors[getUserLanguage()] || detectors.japanese;
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### プロンプトユーティリティ
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import { getPromptTemplate, overridePrompts } from '@aituber-onair/manneri';
|
|
300
|
+
|
|
301
|
+
// 任意の言語の介入プロンプトを取得
|
|
302
|
+
const interventionPrompt = getPromptTemplate(
|
|
303
|
+
myCustomPrompts,
|
|
304
|
+
'zh'
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// 複数言語のプロンプトを上書き
|
|
308
|
+
const globalPrompts = overridePrompts(DEFAULT_PROMPTS, {
|
|
309
|
+
zh: { intervention: ['换个话题吧'] },
|
|
310
|
+
ko: { intervention: ['주제를 바꿔주세요'] },
|
|
311
|
+
es: { intervention: ['Cambiemos de tema'] }
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## 個別機能の使用
|
|
316
|
+
|
|
317
|
+
### 類似度分析
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { SimilarityAnalyzer } from '@aituber-onair/manneri';
|
|
321
|
+
|
|
322
|
+
const analyzer = new SimilarityAnalyzer();
|
|
323
|
+
const similarity = analyzer.calculateSimilarity('こんにちは', 'こんにちは、元気ですか?');
|
|
324
|
+
console.log('類似度:', similarity); // 0.0 - 1.0
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### キーワード抽出
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { KeywordExtractor } from '@aituber-onair/manneri';
|
|
331
|
+
|
|
332
|
+
const extractor = new KeywordExtractor();
|
|
333
|
+
const keywords = extractor.extractKeywordsFromMessages(messages);
|
|
334
|
+
console.log('キーワード:', keywords);
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### パターン検出
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
import { PatternDetector } from '@aituber-onair/manneri';
|
|
341
|
+
|
|
342
|
+
const detector = new PatternDetector();
|
|
343
|
+
const result = detector.detectPatterns(messages);
|
|
344
|
+
console.log('パターン:', result.patterns);
|
|
345
|
+
console.log('重要度:', result.severity);
|
|
346
|
+
console.log('信頼度:', result.confidence);
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## データの永続化
|
|
350
|
+
|
|
351
|
+
Manneriは設定可能なプロバイダーを通じて柔軟な永続化機能を提供します。データの保存方法とタイミングを完全に制御できます。
|
|
352
|
+
|
|
353
|
+
### ブラウザ環境(LocalStorage)
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { ManneriDetector, LocalStoragePersistenceProvider } from '@aituber-onair/manneri';
|
|
357
|
+
|
|
358
|
+
// LocalStorage永続化を設定
|
|
359
|
+
const detector = new ManneriDetector({
|
|
360
|
+
// ... 設定オプション
|
|
361
|
+
}, {
|
|
362
|
+
persistenceProvider: new LocalStoragePersistenceProvider({
|
|
363
|
+
storageKey: 'manneri_data', // カスタムストレージキー
|
|
364
|
+
version: '1.0.0' // データバージョン
|
|
365
|
+
})
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// 手動での永続化制御
|
|
369
|
+
await detector.save(); // 現在の状態を保存
|
|
370
|
+
await detector.load(); // 保存された状態を読込
|
|
371
|
+
await detector.cleanup(); // 古いデータをクリーンアップ
|
|
372
|
+
|
|
373
|
+
// 永続化が利用可能かチェック
|
|
374
|
+
if (detector.hasPersistenceProvider()) {
|
|
375
|
+
console.log('永続化が設定されています');
|
|
376
|
+
|
|
377
|
+
// ストレージ情報を取得
|
|
378
|
+
const info = detector.getPersistenceInfo();
|
|
379
|
+
console.log('ストレージ情報:', info);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// 永続化操作のイベントハンドリング
|
|
383
|
+
detector.on('save_success', ({ timestamp }) => {
|
|
384
|
+
console.log('データ保存成功:', new Date(timestamp));
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
detector.on('save_error', ({ error }) => {
|
|
388
|
+
console.error('データ保存失敗:', error);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
detector.on('load_success', ({ data, timestamp }) => {
|
|
392
|
+
console.log('データ読込成功:', data);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
detector.on('cleanup_completed', ({ removedItems, timestamp }) => {
|
|
396
|
+
console.log(`${removedItems}個の古いアイテムをクリーンアップしました`);
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### カスタム永続化プロバイダー
|
|
401
|
+
|
|
402
|
+
Node.js、Deno、またはカスタムストレージソリューション用に`PersistenceProvider`インターフェースを実装:
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import type { PersistenceProvider, StorageData } from '@aituber-onair/manneri';
|
|
406
|
+
|
|
407
|
+
// 例: データベース永続化プロバイダー
|
|
408
|
+
class DatabasePersistenceProvider implements PersistenceProvider {
|
|
409
|
+
constructor(private dbConnection: any) {}
|
|
410
|
+
|
|
411
|
+
async save(data: StorageData): Promise<boolean> {
|
|
412
|
+
try {
|
|
413
|
+
await this.dbConnection.query(
|
|
414
|
+
'INSERT OR REPLACE INTO manneri_data (id, data) VALUES (?, ?)',
|
|
415
|
+
[1, JSON.stringify(data)]
|
|
416
|
+
);
|
|
417
|
+
return true;
|
|
418
|
+
} catch (error) {
|
|
419
|
+
console.error('データベース保存失敗:', error);
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async load(): Promise<StorageData | null> {
|
|
425
|
+
try {
|
|
426
|
+
const result = await this.dbConnection.query(
|
|
427
|
+
'SELECT data FROM manneri_data WHERE id = ?',
|
|
428
|
+
[1]
|
|
429
|
+
);
|
|
430
|
+
return result.length > 0 ? JSON.parse(result[0].data) : null;
|
|
431
|
+
} catch (error) {
|
|
432
|
+
console.error('データベース読込失敗:', error);
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async clear(): Promise<boolean> {
|
|
438
|
+
try {
|
|
439
|
+
await this.dbConnection.query('DELETE FROM manneri_data WHERE id = ?', [1]);
|
|
440
|
+
return true;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.error('データベースクリア失敗:', error);
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async cleanup(maxAge: number): Promise<number> {
|
|
448
|
+
// ストレージのクリーンアップロジックを実装
|
|
449
|
+
// 削除されたアイテム数を返す
|
|
450
|
+
return 0;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// カスタム永続化プロバイダーを使用
|
|
455
|
+
const detector = new ManneriDetector({
|
|
456
|
+
// ... 設定
|
|
457
|
+
}, {
|
|
458
|
+
persistenceProvider: new DatabasePersistenceProvider(dbConnection)
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### 手動データ管理(永続化なし)
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
// 永続化プロバイダーなしで使用
|
|
466
|
+
const detector = new ManneriDetector();
|
|
467
|
+
|
|
468
|
+
// カスタムストレージ用の手動エクスポート/インポート
|
|
469
|
+
const data = detector.exportData();
|
|
470
|
+
// 任意の方法でデータを保存(ファイル、データベースなど)
|
|
471
|
+
await myCustomStorage.save(data);
|
|
472
|
+
|
|
473
|
+
// データの復元
|
|
474
|
+
const restoredData = await myCustomStorage.load();
|
|
475
|
+
detector.importData(restoredData);
|
|
476
|
+
|
|
477
|
+
// 履歴のクリア
|
|
478
|
+
detector.clearHistory();
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### 環境固有の例
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
// ブラウザのみでLocalStorage使用
|
|
485
|
+
const browserDetector = new ManneriDetector({}, {
|
|
486
|
+
persistenceProvider: new LocalStoragePersistenceProvider()
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Node.jsでファイルストレージ
|
|
490
|
+
class FilePersistenceProvider implements PersistenceProvider {
|
|
491
|
+
constructor(private filePath: string) {}
|
|
492
|
+
|
|
493
|
+
save(data: StorageData): boolean {
|
|
494
|
+
try {
|
|
495
|
+
fs.writeFileSync(this.filePath, JSON.stringify(data));
|
|
496
|
+
return true;
|
|
497
|
+
} catch {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
load(): StorageData | null {
|
|
503
|
+
try {
|
|
504
|
+
const data = fs.readFileSync(this.filePath, 'utf8');
|
|
505
|
+
return JSON.parse(data);
|
|
506
|
+
} catch {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
clear(): boolean {
|
|
512
|
+
try {
|
|
513
|
+
fs.unlinkSync(this.filePath);
|
|
514
|
+
return true;
|
|
515
|
+
} catch {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const nodeDetector = new ManneriDetector({}, {
|
|
522
|
+
persistenceProvider: new FilePersistenceProvider('./manneri-data.json')
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// DenoでDeno.KV使用
|
|
526
|
+
class DenoKvPersistenceProvider implements PersistenceProvider {
|
|
527
|
+
constructor(private kv: Deno.Kv) {}
|
|
528
|
+
|
|
529
|
+
async save(data: StorageData): Promise<boolean> {
|
|
530
|
+
try {
|
|
531
|
+
await this.kv.set(['manneri', 'data'], data);
|
|
532
|
+
return true;
|
|
533
|
+
} catch {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async load(): Promise<StorageData | null> {
|
|
539
|
+
try {
|
|
540
|
+
const result = await this.kv.get(['manneri', 'data']);
|
|
541
|
+
return result.value as StorageData | null;
|
|
542
|
+
} catch {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async clear(): Promise<boolean> {
|
|
548
|
+
try {
|
|
549
|
+
await this.kv.delete(['manneri', 'data']);
|
|
550
|
+
return true;
|
|
551
|
+
} catch {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const kv = await Deno.openKv();
|
|
558
|
+
const denoDetector = new ManneriDetector({}, {
|
|
559
|
+
persistenceProvider: new DenoKvPersistenceProvider(kv)
|
|
560
|
+
});
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
## TypeScript型定義
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
import type {
|
|
567
|
+
Message,
|
|
568
|
+
ManneriConfig,
|
|
569
|
+
AnalysisResult,
|
|
570
|
+
DiversificationPrompt,
|
|
571
|
+
LocalizedPrompts,
|
|
572
|
+
PromptTemplates,
|
|
573
|
+
SupportedLanguage,
|
|
574
|
+
PersistenceProvider,
|
|
575
|
+
PersistenceConfig,
|
|
576
|
+
StorageData
|
|
577
|
+
} from '@aituber-onair/manneri';
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## ブラウザ対応
|
|
581
|
+
|
|
582
|
+
- Chrome 90+
|
|
583
|
+
- Firefox 88+
|
|
584
|
+
- Safari 14+
|
|
585
|
+
- Edge 90+
|
|
586
|
+
|
|
587
|
+
## パフォーマンス
|
|
588
|
+
|
|
589
|
+
- 軽量: gzip圧縮後 < 50KB
|
|
590
|
+
- 高速: リアルタイム分析 < 100ms
|
|
591
|
+
- メモリ効率: 自動キャッシュクリーンアップ
|
|
592
|
+
|
|
593
|
+
## ライセンス
|
|
594
|
+
|
|
595
|
+
MIT License
|
|
596
|
+
|
|
597
|
+
## 貢献
|
|
598
|
+
|
|
599
|
+
プルリクエストやIssueを歓迎します。詳細は [CONTRIBUTING.md](CONTRIBUTING.md) をご覧ください。
|
|
600
|
+
|
|
601
|
+
## サポート
|
|
602
|
+
|
|
603
|
+
- GitHub Issues: https://github.com/shinshin86/aituber-onair/issues
|
|
604
|
+
- ドキュメント: https://github.com/shinshin86/aituber-onair/tree/main/packages/manneri
|
|
605
|
+
|
|
606
|
+
## 関連プロジェクト
|
|
607
|
+
|
|
608
|
+
- [AITuber OnAir](https://github.com/shinshin86/aituber-onair) - メインプロジェクト
|
|
609
|
+
- [@aituber-onair/core](https://github.com/shinshin86/aituber-onair/tree/main/packages/core) - コアライブラリ
|