docufresh-ai 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/LICENSE +21 -0
- package/README.md +244 -0
- package/package.json +52 -0
- package/src/ai-engine.js +295 -0
- package/src/index.d.ts +181 -0
- package/src/index.js +257 -0
- package/src/markers.js +133 -0
- package/src/wikipedia.js +185 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vamsi Manthena
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# DocuFresh-AI
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/docufresh-ai)
|
|
4
|
+
[](https://github.com/manthenavamsi/docufresh-ai/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
AI-powered content freshening for [docufresh](https://www.npmjs.com/package/docufresh). Keep your documents updated with real-world facts using **Wikipedia + browser-based AI**.
|
|
7
|
+
|
|
8
|
+
**No API keys required!** The AI runs entirely in your browser or Node.js.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Wikipedia Integration** - Fetch current facts from Wikipedia (free, no auth)
|
|
13
|
+
- **Browser-Based AI** - Uses [Transformers.js](https://huggingface.co/docs/transformers.js) to run AI locally
|
|
14
|
+
- **No API Keys** - Everything runs locally, no OpenAI/Gemini keys needed
|
|
15
|
+
- **Smart Rewriting** - AI rewrites sentences to incorporate fresh facts naturally
|
|
16
|
+
- **Paragraph Generation** - Generate entire paragraphs about topics
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install docufresh-ai
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Note:** Requires Node.js 18+ (for native fetch support).
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
import DocuFreshAI from 'docufresh-ai';
|
|
30
|
+
|
|
31
|
+
const ai = new DocuFreshAI();
|
|
32
|
+
|
|
33
|
+
// First call downloads the AI model (~250MB)
|
|
34
|
+
await ai.init();
|
|
35
|
+
|
|
36
|
+
// Get facts from Wikipedia
|
|
37
|
+
const text = 'Albert Einstein: {{ai_fact:Albert_Einstein}}';
|
|
38
|
+
console.log(await ai.process(text));
|
|
39
|
+
// "Albert Einstein: Albert Einstein was a German-born theoretical physicist..."
|
|
40
|
+
|
|
41
|
+
// Rewrite sentences with current facts
|
|
42
|
+
const text2 = '{{ai_rewrite:World_population:The world has X people}}';
|
|
43
|
+
console.log(await ai.process(text2));
|
|
44
|
+
// "The world has approximately 8.1 billion people"
|
|
45
|
+
|
|
46
|
+
// Generate paragraphs
|
|
47
|
+
const text3 = '{{ai_paragraph:SpaceX}}';
|
|
48
|
+
console.log(await ai.process(text3));
|
|
49
|
+
// "SpaceX is an American spacecraft manufacturer..."
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## AI Markers
|
|
53
|
+
|
|
54
|
+
| Marker | Description | Example |
|
|
55
|
+
|--------|-------------|---------|
|
|
56
|
+
| `{{ai_fact:topic}}` | Get Wikipedia fact | `{{ai_fact:Moon}}` |
|
|
57
|
+
| `{{ai_describe:topic}}` | Short description | `{{ai_describe:Python_(programming_language)}}` |
|
|
58
|
+
| `{{ai_rewrite:topic:template}}` | Rewrite with facts (X = placeholder) | `{{ai_rewrite:Bitcoin:Bitcoin is worth X}}` |
|
|
59
|
+
| `{{ai_paragraph:topic}}` | Generate paragraph | `{{ai_paragraph:Climate_change}}` |
|
|
60
|
+
| `{{ai_summary:topic}}` | Condensed summary | `{{ai_summary:Artificial_intelligence}}` |
|
|
61
|
+
| `{{ai_answer:topic:question}}` | Answer question | `{{ai_answer:Sun:How hot is it?}}` |
|
|
62
|
+
| `{{ai_link:topic}}` | Wikipedia URL | `{{ai_link:JavaScript}}` |
|
|
63
|
+
| `{{ai_updated:topic}}` | Last update date | `{{ai_updated:Tesla,_Inc.}}` |
|
|
64
|
+
|
|
65
|
+
## API Reference
|
|
66
|
+
|
|
67
|
+
### `new DocuFreshAI(options?)`
|
|
68
|
+
|
|
69
|
+
Create a new instance.
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const ai = new DocuFreshAI({
|
|
73
|
+
model: 'Xenova/flan-t5-small', // AI model (default)
|
|
74
|
+
cacheTTL: 1000 * 60 * 60, // Wikipedia cache: 1 hour
|
|
75
|
+
onProgress: (p) => console.log(p) // Model download progress
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `ai.init()`
|
|
80
|
+
|
|
81
|
+
Initialize the AI engine. Downloads the model on first run (~250MB).
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
await ai.init();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `ai.process(text, customData?)`
|
|
88
|
+
|
|
89
|
+
Process text with AI markers.
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
const result = await ai.process('{{ai_fact:Mars}}');
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `ai.processWithDocufresh(text, customData?)`
|
|
96
|
+
|
|
97
|
+
Process with both AI markers and [docufresh](https://www.npmjs.com/package/docufresh) basic markers.
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
// Requires: npm install docufresh
|
|
101
|
+
const result = await ai.processWithDocufresh(
|
|
102
|
+
'{{current_year}}: {{ai_fact:Moon}}'
|
|
103
|
+
);
|
|
104
|
+
// "2026: The Moon is Earth's only natural satellite..."
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Direct Methods
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// Get Wikipedia fact
|
|
111
|
+
const fact = await ai.getFact('JavaScript');
|
|
112
|
+
|
|
113
|
+
// Get full summary
|
|
114
|
+
const summary = await ai.getSummary('React_(JavaScript_library)');
|
|
115
|
+
|
|
116
|
+
// Search Wikipedia
|
|
117
|
+
const results = await ai.search('programming languages', 5);
|
|
118
|
+
|
|
119
|
+
// Rewrite with AI
|
|
120
|
+
const rewritten = await ai.rewrite(
|
|
121
|
+
'JavaScript was created in 1995',
|
|
122
|
+
'JS has been around for X years'
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Generate paragraph
|
|
126
|
+
const paragraph = await ai.generateParagraph('Node.js', 'Runtime for JavaScript');
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Use Cases
|
|
130
|
+
|
|
131
|
+
### Keep Blog Posts Fresh
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
const blogPost = `
|
|
135
|
+
# Tech Industry Update
|
|
136
|
+
|
|
137
|
+
{{ai_paragraph:Artificial_intelligence}}
|
|
138
|
+
|
|
139
|
+
Current AI trends show {{ai_rewrite:ChatGPT:ChatGPT has X users}}.
|
|
140
|
+
|
|
141
|
+
Last updated: {{ai_updated:Artificial_intelligence}}
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
const fresh = await ai.process(blogPost);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Dynamic Documentation
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
const docs = `
|
|
151
|
+
## About Python
|
|
152
|
+
|
|
153
|
+
{{ai_fact:Python_(programming_language)}}
|
|
154
|
+
|
|
155
|
+
**Latest version info:** {{ai_rewrite:Python_(programming_language):Python X is the latest}}
|
|
156
|
+
|
|
157
|
+
[Learn more]({{ai_link:Python_(programming_language)}})
|
|
158
|
+
`;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Q&A Sections
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const faq = `
|
|
165
|
+
**Q: How far is the Moon?**
|
|
166
|
+
A: {{ai_answer:Moon:How far is it from Earth?}}
|
|
167
|
+
|
|
168
|
+
**Q: What is the Sun made of?**
|
|
169
|
+
A: {{ai_answer:Sun:What is it made of?}}
|
|
170
|
+
`;
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Browser Usage
|
|
174
|
+
|
|
175
|
+
```html
|
|
176
|
+
<script type="module">
|
|
177
|
+
import DocuFreshAI from 'https://esm.sh/docufresh-ai';
|
|
178
|
+
|
|
179
|
+
const ai = new DocuFreshAI({
|
|
180
|
+
onProgress: (p) => {
|
|
181
|
+
if (p.status === 'progress') {
|
|
182
|
+
console.log(`Loading: ${Math.round(p.loaded/p.total*100)}%`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
document.getElementById('content').innerHTML = await ai.process(
|
|
188
|
+
document.getElementById('content').innerHTML
|
|
189
|
+
);
|
|
190
|
+
</script>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Available Models
|
|
194
|
+
|
|
195
|
+
| Model | Size | Quality | Speed |
|
|
196
|
+
|-------|------|---------|-------|
|
|
197
|
+
| `Xenova/flan-t5-small` | ~250MB | Good | Fast |
|
|
198
|
+
| `Xenova/flan-t5-base` | ~900MB | Better | Slower |
|
|
199
|
+
| `Xenova/flan-t5-large` | ~3GB | Best | Slowest |
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
const ai = new DocuFreshAI({ model: 'Xenova/flan-t5-base' });
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Caching
|
|
206
|
+
|
|
207
|
+
- Wikipedia responses are cached for 1 hour by default
|
|
208
|
+
- AI model is cached after first download (browser localStorage / Node.js cache)
|
|
209
|
+
- Customize cache TTL:
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
const ai = new DocuFreshAI({
|
|
213
|
+
cacheTTL: 1000 * 60 * 30 // 30 minutes
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Clear cache manually
|
|
217
|
+
ai.clearCache();
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## TypeScript
|
|
221
|
+
|
|
222
|
+
Full TypeScript support included:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import DocuFreshAI, { WikipediaSummary } from 'docufresh-ai';
|
|
226
|
+
|
|
227
|
+
const ai = new DocuFreshAI();
|
|
228
|
+
const summary: WikipediaSummary = await ai.getSummary('TypeScript');
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Requirements
|
|
232
|
+
|
|
233
|
+
- **Node.js 18+** (for native fetch)
|
|
234
|
+
- **Modern browser** (Chrome 89+, Firefox 89+, Safari 15+)
|
|
235
|
+
- **~250MB** disk space for default AI model
|
|
236
|
+
|
|
237
|
+
## Related
|
|
238
|
+
|
|
239
|
+
- [docufresh](https://www.npmjs.com/package/docufresh) - Base library for simple markers
|
|
240
|
+
- [Transformers.js](https://huggingface.co/docs/transformers.js) - The AI engine
|
|
241
|
+
|
|
242
|
+
## License
|
|
243
|
+
|
|
244
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "docufresh-ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-powered content freshening for docufresh - Uses Wikipedia + browser-based AI (no API keys needed)",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./src/index.js",
|
|
11
|
+
"types": "./src/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18.0.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node tests/ai.test.js",
|
|
22
|
+
"prepublishOnly": "npm test"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"docufresh",
|
|
26
|
+
"ai",
|
|
27
|
+
"transformers",
|
|
28
|
+
"wikipedia",
|
|
29
|
+
"content-update",
|
|
30
|
+
"fresh-content",
|
|
31
|
+
"browser-ai",
|
|
32
|
+
"text-generation",
|
|
33
|
+
"fact-checking",
|
|
34
|
+
"dynamic-content"
|
|
35
|
+
],
|
|
36
|
+
"author": "Vamsi Manthena <manthenavamsi@gmail.com>",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/manthenavamsi/docufresh-ai.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/manthenavamsi/docufresh-ai/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/manthenavamsi/docufresh-ai#readme",
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"docufresh": ">=0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@huggingface/transformers": "^3.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/ai-engine.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Engine
|
|
3
|
+
* Uses Transformers.js for browser-based text generation
|
|
4
|
+
* No API keys required - models run locally!
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { pipeline, env } from '@huggingface/transformers';
|
|
8
|
+
|
|
9
|
+
// Configure Transformers.js for both Node.js and browser
|
|
10
|
+
env.allowLocalModels = false; // Always download from Hugging Face
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Supported models with metadata
|
|
14
|
+
*/
|
|
15
|
+
const SUPPORTED_MODELS = {
|
|
16
|
+
'small': {
|
|
17
|
+
id: 'Xenova/flan-t5-small',
|
|
18
|
+
name: 'FLAN-T5 Small',
|
|
19
|
+
size: '250MB',
|
|
20
|
+
quality: 'good',
|
|
21
|
+
description: 'Fast, lightweight model for simple tasks'
|
|
22
|
+
},
|
|
23
|
+
'base': {
|
|
24
|
+
id: 'Xenova/flan-t5-base',
|
|
25
|
+
name: 'FLAN-T5 Base',
|
|
26
|
+
size: '900MB',
|
|
27
|
+
quality: 'better',
|
|
28
|
+
description: 'Balanced model for most use cases'
|
|
29
|
+
},
|
|
30
|
+
'large': {
|
|
31
|
+
id: 'Xenova/flan-t5-large',
|
|
32
|
+
name: 'FLAN-T5 Large',
|
|
33
|
+
size: '3GB',
|
|
34
|
+
quality: 'best',
|
|
35
|
+
description: 'Highest quality, requires more memory'
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Only enable browser cache in browser environment
|
|
40
|
+
if (typeof window !== 'undefined') {
|
|
41
|
+
env.useBrowserCache = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class AIEngine {
|
|
45
|
+
/**
|
|
46
|
+
* Get list of available model keys
|
|
47
|
+
* @returns {string[]} Array of model keys ('small', 'base', 'large')
|
|
48
|
+
*/
|
|
49
|
+
static getAvailableModels() {
|
|
50
|
+
return Object.keys(SUPPORTED_MODELS);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get information about a specific model
|
|
55
|
+
* @param {string} modelKey - Model key ('small', 'base', 'large')
|
|
56
|
+
* @returns {object|null} Model info object or null if not found
|
|
57
|
+
*/
|
|
58
|
+
static getModelInfo(modelKey) {
|
|
59
|
+
return SUPPORTED_MODELS[modelKey] || null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
// Default to 'small' model - users can switch to 'base' or 'large' for better quality
|
|
64
|
+
this.model = this.resolveModel(options.model || 'small');
|
|
65
|
+
this.generator = null;
|
|
66
|
+
this.initialized = false;
|
|
67
|
+
this.initializing = false;
|
|
68
|
+
this.onProgress = options.onProgress || null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a model input to a full Hugging Face model ID
|
|
73
|
+
* - 'small' (default), 'base', 'large' -> resolves to predefined model
|
|
74
|
+
* - Custom model ID (e.g., 'facebook/bart-large-cnn') -> used as-is
|
|
75
|
+
* @param {string} modelInput - Model key or custom model ID
|
|
76
|
+
* @returns {string} Full Hugging Face model ID
|
|
77
|
+
*/
|
|
78
|
+
resolveModel(modelInput) {
|
|
79
|
+
// Check if it's a predefined model key
|
|
80
|
+
if (SUPPORTED_MODELS[modelInput]) {
|
|
81
|
+
return SUPPORTED_MODELS[modelInput].id;
|
|
82
|
+
}
|
|
83
|
+
// Otherwise, treat as custom model ID
|
|
84
|
+
return modelInput;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Initialize the AI model
|
|
89
|
+
* Downloads and loads the model (first time may take a while)
|
|
90
|
+
*/
|
|
91
|
+
async init() {
|
|
92
|
+
if (this.initialized) return;
|
|
93
|
+
if (this.initializing) {
|
|
94
|
+
// Wait for existing initialization
|
|
95
|
+
while (this.initializing) {
|
|
96
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.initializing = true;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
console.log(`Loading AI model: ${this.model}...`);
|
|
105
|
+
|
|
106
|
+
// Create text2text-generation pipeline
|
|
107
|
+
this.generator = await pipeline('text2text-generation', this.model, {
|
|
108
|
+
progress_callback: (progress) => {
|
|
109
|
+
if (this.onProgress) {
|
|
110
|
+
this.onProgress(progress);
|
|
111
|
+
}
|
|
112
|
+
if (progress.status === 'progress') {
|
|
113
|
+
const percent = Math.round((progress.loaded / progress.total) * 100);
|
|
114
|
+
console.log(`Downloading model: ${percent}%`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.initialized = true;
|
|
120
|
+
console.log('AI model loaded successfully!');
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Failed to initialize AI engine:', error.message);
|
|
123
|
+
throw error;
|
|
124
|
+
} finally {
|
|
125
|
+
this.initializing = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Rewrite a sentence incorporating a fact
|
|
131
|
+
* @param {string} fact - The fact to incorporate
|
|
132
|
+
* @param {string} template - Template sentence (X marks where fact goes)
|
|
133
|
+
* @returns {Promise<string>} - Rewritten sentence
|
|
134
|
+
*/
|
|
135
|
+
async rewriteSentence(fact, template) {
|
|
136
|
+
await this.init();
|
|
137
|
+
|
|
138
|
+
// If template has X placeholder, do simple replacement first
|
|
139
|
+
let baseText = template;
|
|
140
|
+
if (template.includes('X') || template.includes('x')) {
|
|
141
|
+
// Extract key info from fact (first sentence or first 100 chars)
|
|
142
|
+
const keyInfo = this.extractKeyInfo(fact);
|
|
143
|
+
baseText = template.replace(/\bX\b/gi, keyInfo);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Use AI to polish the sentence
|
|
147
|
+
const prompt = `Rewrite this sentence to be more natural and informative: "${baseText}"
|
|
148
|
+
Based on this fact: ${fact.slice(0, 500)}
|
|
149
|
+
Output only the rewritten sentence:`;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const result = await this.generator(prompt, {
|
|
153
|
+
max_new_tokens: 100,
|
|
154
|
+
temperature: 0.7,
|
|
155
|
+
do_sample: true
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const output = result[0]?.generated_text?.trim();
|
|
159
|
+
return output || baseText;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('AI rewrite error:', error.message);
|
|
162
|
+
return baseText; // Return template with placeholder filled
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Generate a paragraph about a topic using facts
|
|
168
|
+
* @param {string} topic - The topic name
|
|
169
|
+
* @param {string} facts - Facts about the topic
|
|
170
|
+
* @returns {Promise<string>} - Generated paragraph
|
|
171
|
+
*/
|
|
172
|
+
async generateParagraph(topic, facts) {
|
|
173
|
+
await this.init();
|
|
174
|
+
|
|
175
|
+
const prompt = `Write a brief, informative paragraph about ${topic} based on these facts:
|
|
176
|
+
${facts.slice(0, 800)}
|
|
177
|
+
|
|
178
|
+
Write a 2-3 sentence summary:`;
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const result = await this.generator(prompt, {
|
|
182
|
+
max_new_tokens: 150,
|
|
183
|
+
temperature: 0.7,
|
|
184
|
+
do_sample: true
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const output = result[0]?.generated_text?.trim();
|
|
188
|
+
return output || facts.slice(0, 200);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('AI generate error:', error.message);
|
|
191
|
+
return facts.slice(0, 200); // Return truncated facts as fallback
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Summarize text
|
|
197
|
+
* @param {string} text - Text to summarize
|
|
198
|
+
* @param {number} maxLength - Max length of summary
|
|
199
|
+
* @returns {Promise<string>} - Summary
|
|
200
|
+
*/
|
|
201
|
+
async summarize(text, maxLength = 100) {
|
|
202
|
+
await this.init();
|
|
203
|
+
|
|
204
|
+
const prompt = `Summarize this in one sentence: ${text.slice(0, 1000)}`;
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const result = await this.generator(prompt, {
|
|
208
|
+
max_new_tokens: maxLength,
|
|
209
|
+
temperature: 0.5
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return result[0]?.generated_text?.trim() || text.slice(0, maxLength);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('AI summarize error:', error.message);
|
|
215
|
+
return text.slice(0, maxLength);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Answer a question based on context
|
|
221
|
+
* @param {string} question - The question
|
|
222
|
+
* @param {string} context - Context to answer from
|
|
223
|
+
* @returns {Promise<string>} - Answer
|
|
224
|
+
*/
|
|
225
|
+
async answerQuestion(question, context) {
|
|
226
|
+
await this.init();
|
|
227
|
+
|
|
228
|
+
const prompt = `Answer this question based on the context.
|
|
229
|
+
Context: ${context.slice(0, 800)}
|
|
230
|
+
Question: ${question}
|
|
231
|
+
Answer:`;
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const result = await this.generator(prompt, {
|
|
235
|
+
max_new_tokens: 100,
|
|
236
|
+
temperature: 0.3
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return result[0]?.generated_text?.trim() || 'Unable to answer';
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error('AI answer error:', error.message);
|
|
242
|
+
return 'Unable to answer';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Extract key information from a fact (first sentence or key phrase)
|
|
248
|
+
*/
|
|
249
|
+
extractKeyInfo(fact) {
|
|
250
|
+
// Get first sentence
|
|
251
|
+
const firstSentence = fact.split(/[.!?]/)[0];
|
|
252
|
+
|
|
253
|
+
// If it's short enough, use it
|
|
254
|
+
if (firstSentence.length <= 100) {
|
|
255
|
+
return firstSentence.trim();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Otherwise extract key numbers or phrases
|
|
259
|
+
const numbers = fact.match(/[\d,]+(\.\d+)?(\s*(million|billion|trillion|percent|%|years|days))?/gi);
|
|
260
|
+
if (numbers && numbers.length > 0) {
|
|
261
|
+
return numbers[0];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Fallback to truncated first sentence
|
|
265
|
+
return firstSentence.slice(0, 100).trim() + '...';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Check if the engine is ready
|
|
270
|
+
*/
|
|
271
|
+
isReady() {
|
|
272
|
+
return this.initialized;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get the current model name
|
|
277
|
+
*/
|
|
278
|
+
getModel() {
|
|
279
|
+
return this.model;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Change the model (requires re-initialization)
|
|
284
|
+
* @param {string} modelName - 'small', 'base', 'large', or custom model ID
|
|
285
|
+
*/
|
|
286
|
+
async setModel(modelName) {
|
|
287
|
+
this.model = this.resolveModel(modelName);
|
|
288
|
+
this.initialized = false;
|
|
289
|
+
this.generator = null;
|
|
290
|
+
await this.init();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export { AIEngine };
|
|
295
|
+
export default AIEngine;
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocuFresh-AI TypeScript Declarations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface WikipediaSummary {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
extract: string;
|
|
9
|
+
extractShort: string;
|
|
10
|
+
thumbnail: string | null;
|
|
11
|
+
url: string | null;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
error?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface WikipediaSearchResult {
|
|
17
|
+
title: string;
|
|
18
|
+
description: string;
|
|
19
|
+
url: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface WikipediaClientOptions {
|
|
23
|
+
cacheTTL?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Predefined model keys
|
|
28
|
+
*/
|
|
29
|
+
export type ModelKey = 'small' | 'base' | 'large';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Model information
|
|
33
|
+
*/
|
|
34
|
+
export interface ModelInfo {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
size: string;
|
|
38
|
+
quality: 'good' | 'better' | 'best';
|
|
39
|
+
description: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export declare class WikipediaClient {
|
|
43
|
+
constructor(options?: WikipediaClientOptions);
|
|
44
|
+
getSummary(topic: string): Promise<WikipediaSummary>;
|
|
45
|
+
getFact(topic: string): Promise<string>;
|
|
46
|
+
getDescription(topic: string): Promise<string>;
|
|
47
|
+
search(query: string, limit?: number): Promise<WikipediaSearchResult[]>;
|
|
48
|
+
normalizeTopic(topic: string): string;
|
|
49
|
+
clearCache(): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface AIEngineOptions {
|
|
53
|
+
model?: ModelKey | string;
|
|
54
|
+
onProgress?: (progress: any) => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export declare class AIEngine {
|
|
58
|
+
/**
|
|
59
|
+
* Get list of available model keys
|
|
60
|
+
*/
|
|
61
|
+
static getAvailableModels(): ModelKey[];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get information about a specific model
|
|
65
|
+
*/
|
|
66
|
+
static getModelInfo(modelKey: ModelKey): ModelInfo | null;
|
|
67
|
+
|
|
68
|
+
constructor(options?: AIEngineOptions);
|
|
69
|
+
init(): Promise<void>;
|
|
70
|
+
rewriteSentence(fact: string, template: string): Promise<string>;
|
|
71
|
+
generateParagraph(topic: string, facts: string): Promise<string>;
|
|
72
|
+
summarize(text: string, maxLength?: number): Promise<string>;
|
|
73
|
+
answerQuestion(question: string, context: string): Promise<string>;
|
|
74
|
+
isReady(): boolean;
|
|
75
|
+
getModel(): string;
|
|
76
|
+
setModel(modelName: ModelKey | string): Promise<void>;
|
|
77
|
+
resolveModel(modelInput: ModelKey | string): string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface AIMarkers {
|
|
81
|
+
ai_fact: (topic: string) => Promise<string>;
|
|
82
|
+
ai_describe: (topic: string) => Promise<string>;
|
|
83
|
+
ai_rewrite: (topic: string, template: string) => Promise<string>;
|
|
84
|
+
ai_paragraph: (topic: string) => Promise<string>;
|
|
85
|
+
ai_summary: (topic: string) => Promise<string>;
|
|
86
|
+
ai_answer: (topic: string, question: string) => Promise<string>;
|
|
87
|
+
ai_link: (topic: string) => Promise<string>;
|
|
88
|
+
ai_updated: (topic: string) => Promise<string>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export declare function createAIMarkers(
|
|
92
|
+
wikipedia: WikipediaClient,
|
|
93
|
+
ai: AIEngine
|
|
94
|
+
): AIMarkers;
|
|
95
|
+
|
|
96
|
+
export declare function registerAIMarkers(
|
|
97
|
+
docufresh: any,
|
|
98
|
+
wikipedia: WikipediaClient,
|
|
99
|
+
ai: AIEngine
|
|
100
|
+
): AIMarkers;
|
|
101
|
+
|
|
102
|
+
export interface DocuFreshAIOptions {
|
|
103
|
+
/** Model to use: 'small' (default), 'base', 'large', or custom model ID */
|
|
104
|
+
model?: ModelKey | string;
|
|
105
|
+
cacheTTL?: number;
|
|
106
|
+
onProgress?: (progress: any) => void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export declare class DocuFreshAI {
|
|
110
|
+
/**
|
|
111
|
+
* Get list of available model keys
|
|
112
|
+
*/
|
|
113
|
+
static getAvailableModels(): ModelKey[];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get information about a specific model
|
|
117
|
+
*/
|
|
118
|
+
static getModelInfo(modelKey: ModelKey): ModelInfo | null;
|
|
119
|
+
|
|
120
|
+
constructor(options?: DocuFreshAIOptions);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Initialize the AI engine (downloads model on first run)
|
|
124
|
+
*/
|
|
125
|
+
init(): Promise<void>;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Process text with AI markers
|
|
129
|
+
* @param text - Text containing {{ai_*}} markers
|
|
130
|
+
* @param customData - Optional custom data for variable replacement
|
|
131
|
+
*/
|
|
132
|
+
process(text: string, customData?: Record<string, string | number>): Promise<string>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Process text with both docufresh and AI markers
|
|
136
|
+
* Requires docufresh to be installed
|
|
137
|
+
*/
|
|
138
|
+
processWithDocufresh(text: string, customData?: Record<string, string | number>): Promise<string>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get a fact from Wikipedia
|
|
142
|
+
*/
|
|
143
|
+
getFact(topic: string): Promise<string>;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get a summary from Wikipedia
|
|
147
|
+
*/
|
|
148
|
+
getSummary(topic: string): Promise<WikipediaSummary>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Search Wikipedia
|
|
152
|
+
*/
|
|
153
|
+
search(query: string, limit?: number): Promise<WikipediaSearchResult[]>;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Rewrite text using AI
|
|
157
|
+
*/
|
|
158
|
+
rewrite(fact: string, template: string): Promise<string>;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Generate a paragraph using AI
|
|
162
|
+
*/
|
|
163
|
+
generateParagraph(topic: string, context: string): Promise<string>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if the AI engine is ready
|
|
167
|
+
*/
|
|
168
|
+
isReady(): boolean;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get the current AI model name
|
|
172
|
+
*/
|
|
173
|
+
getModel(): string;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Clear the Wikipedia cache
|
|
177
|
+
*/
|
|
178
|
+
clearCache(): void;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export default DocuFreshAI;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocuFresh-AI
|
|
3
|
+
* AI-powered content freshening using Wikipedia + browser-based AI
|
|
4
|
+
* No API keys required!
|
|
5
|
+
*
|
|
6
|
+
* @version 0.1.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { WikipediaClient } from './wikipedia.js';
|
|
10
|
+
import { AIEngine } from './ai-engine.js';
|
|
11
|
+
import { registerAIMarkers, createAIMarkers } from './markers.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* DocuFreshAI - Main class for AI-powered content freshening
|
|
15
|
+
*/
|
|
16
|
+
class DocuFreshAI {
|
|
17
|
+
/**
|
|
18
|
+
* Get list of available model keys
|
|
19
|
+
* @returns {string[]} Array of model keys ('small', 'base', 'large')
|
|
20
|
+
* @example
|
|
21
|
+
* DocuFreshAI.getAvailableModels(); // ['small', 'base', 'large']
|
|
22
|
+
*/
|
|
23
|
+
static getAvailableModels() {
|
|
24
|
+
return AIEngine.getAvailableModels();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get information about a specific model
|
|
29
|
+
* @param {string} modelKey - Model key ('small', 'base', 'large')
|
|
30
|
+
* @returns {object|null} Model info with id, name, size, quality, description
|
|
31
|
+
* @example
|
|
32
|
+
* DocuFreshAI.getModelInfo('base');
|
|
33
|
+
* // { id: 'Xenova/flan-t5-base', name: 'FLAN-T5 Base', size: '900MB', ... }
|
|
34
|
+
*/
|
|
35
|
+
static getModelInfo(modelKey) {
|
|
36
|
+
return AIEngine.getModelInfo(modelKey);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a DocuFreshAI instance
|
|
41
|
+
* @param {object} options - Configuration options
|
|
42
|
+
* @param {string} options.model - Model to use: 'small' (default), 'base', 'large', or custom model ID
|
|
43
|
+
* @param {number} options.cacheTTL - Wikipedia cache TTL in ms (default: 1 hour)
|
|
44
|
+
* @param {function} options.onProgress - Callback for model download progress
|
|
45
|
+
*/
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
this.options = options;
|
|
48
|
+
this.wikipedia = new WikipediaClient({
|
|
49
|
+
cacheTTL: options.cacheTTL
|
|
50
|
+
});
|
|
51
|
+
this.ai = new AIEngine({
|
|
52
|
+
model: options.model,
|
|
53
|
+
onProgress: options.onProgress
|
|
54
|
+
});
|
|
55
|
+
this.markers = null;
|
|
56
|
+
this.ready = false;
|
|
57
|
+
this.docufresh = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Initialize the AI engine
|
|
62
|
+
* This downloads and loads the AI model (may take time on first run)
|
|
63
|
+
* @returns {Promise<void>}
|
|
64
|
+
*/
|
|
65
|
+
async init() {
|
|
66
|
+
if (this.ready) return;
|
|
67
|
+
|
|
68
|
+
console.log('Initializing DocuFresh-AI...');
|
|
69
|
+
|
|
70
|
+
// Initialize AI engine (downloads model)
|
|
71
|
+
await this.ai.init();
|
|
72
|
+
|
|
73
|
+
// Create markers
|
|
74
|
+
this.markers = createAIMarkers(this.wikipedia, this.ai);
|
|
75
|
+
|
|
76
|
+
this.ready = true;
|
|
77
|
+
console.log('DocuFresh-AI ready!');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Process text with AI markers
|
|
82
|
+
* Handles async markers that fetch from Wikipedia and generate with AI
|
|
83
|
+
*
|
|
84
|
+
* @param {string} text - Text containing {{ai_*}} markers
|
|
85
|
+
* @param {object} customData - Optional custom data for basic markers
|
|
86
|
+
* @returns {Promise<string>} - Processed text
|
|
87
|
+
*/
|
|
88
|
+
async process(text, customData = {}) {
|
|
89
|
+
if (!this.ready) {
|
|
90
|
+
await this.init();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let result = text;
|
|
94
|
+
|
|
95
|
+
// Replace custom data first (simple sync replacement)
|
|
96
|
+
for (const [key, value] of Object.entries(customData)) {
|
|
97
|
+
const pattern = new RegExp(`{{${key}}}`, 'g');
|
|
98
|
+
result = result.replace(pattern, String(value));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Find all AI markers
|
|
102
|
+
const markerPattern = /{{(ai_\w+):([^}]*)}}/g;
|
|
103
|
+
const matches = [...result.matchAll(markerPattern)];
|
|
104
|
+
|
|
105
|
+
// Process each AI marker
|
|
106
|
+
for (const match of matches) {
|
|
107
|
+
const fullMarker = match[0];
|
|
108
|
+
const markerName = match[1];
|
|
109
|
+
const paramsString = match[2];
|
|
110
|
+
|
|
111
|
+
// Split parameters by : (but not within the params themselves)
|
|
112
|
+
const params = paramsString.split(':').map(p => p.trim());
|
|
113
|
+
|
|
114
|
+
const markerFn = this.markers[markerName];
|
|
115
|
+
|
|
116
|
+
if (markerFn) {
|
|
117
|
+
try {
|
|
118
|
+
const replacement = await markerFn(...params);
|
|
119
|
+
result = result.replace(fullMarker, replacement);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(`Error processing ${markerName}:`, error.message);
|
|
122
|
+
// Leave marker unchanged on error
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Also handle simple ai_* markers without params
|
|
128
|
+
const simplePattern = /{{(ai_\w+)}}/g;
|
|
129
|
+
const simpleMatches = [...result.matchAll(simplePattern)];
|
|
130
|
+
|
|
131
|
+
for (const match of simpleMatches) {
|
|
132
|
+
const fullMarker = match[0];
|
|
133
|
+
const markerName = match[1];
|
|
134
|
+
|
|
135
|
+
const markerFn = this.markers[markerName];
|
|
136
|
+
|
|
137
|
+
if (markerFn) {
|
|
138
|
+
try {
|
|
139
|
+
const replacement = await markerFn();
|
|
140
|
+
result = result.replace(fullMarker, replacement);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(`Error processing ${markerName}:`, error.message);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Process text and also use base docufresh markers
|
|
152
|
+
* Requires docufresh to be installed
|
|
153
|
+
*
|
|
154
|
+
* @param {string} text - Text with both regular and AI markers
|
|
155
|
+
* @param {object} customData - Custom data
|
|
156
|
+
* @returns {Promise<string>} - Processed text
|
|
157
|
+
*/
|
|
158
|
+
async processWithDocufresh(text, customData = {}) {
|
|
159
|
+
// Lazy load docufresh
|
|
160
|
+
if (!this.docufresh) {
|
|
161
|
+
try {
|
|
162
|
+
const docufreshModule = await import('docufresh');
|
|
163
|
+
this.docufresh = docufreshModule.default || docufreshModule;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.warn('docufresh not installed, skipping basic markers');
|
|
166
|
+
return this.process(text, customData);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// First process with base docufresh (sync markers)
|
|
171
|
+
let result = this.docufresh.process(text, customData);
|
|
172
|
+
|
|
173
|
+
// Then process AI markers (async)
|
|
174
|
+
result = await this.process(result, {});
|
|
175
|
+
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get a fact from Wikipedia
|
|
181
|
+
* Convenience method for direct access
|
|
182
|
+
*
|
|
183
|
+
* @param {string} topic - Wikipedia topic
|
|
184
|
+
* @returns {Promise<string>} - Fact text
|
|
185
|
+
*/
|
|
186
|
+
async getFact(topic) {
|
|
187
|
+
return this.wikipedia.getFact(topic);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get a summary from Wikipedia
|
|
192
|
+
* @param {string} topic - Wikipedia topic
|
|
193
|
+
* @returns {Promise<object>} - Summary object
|
|
194
|
+
*/
|
|
195
|
+
async getSummary(topic) {
|
|
196
|
+
return this.wikipedia.getSummary(topic);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Search Wikipedia
|
|
201
|
+
* @param {string} query - Search query
|
|
202
|
+
* @param {number} limit - Max results
|
|
203
|
+
* @returns {Promise<Array>} - Search results
|
|
204
|
+
*/
|
|
205
|
+
async search(query, limit = 5) {
|
|
206
|
+
return this.wikipedia.search(query, limit);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Rewrite text using AI
|
|
211
|
+
* @param {string} fact - Fact to incorporate
|
|
212
|
+
* @param {string} template - Template with X placeholder
|
|
213
|
+
* @returns {Promise<string>} - Rewritten text
|
|
214
|
+
*/
|
|
215
|
+
async rewrite(fact, template) {
|
|
216
|
+
if (!this.ready) await this.init();
|
|
217
|
+
return this.ai.rewriteSentence(fact, template);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Generate a paragraph using AI
|
|
222
|
+
* @param {string} topic - Topic name
|
|
223
|
+
* @param {string} context - Context/facts
|
|
224
|
+
* @returns {Promise<string>} - Generated paragraph
|
|
225
|
+
*/
|
|
226
|
+
async generateParagraph(topic, context) {
|
|
227
|
+
if (!this.ready) await this.init();
|
|
228
|
+
return this.ai.generateParagraph(topic, context);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if the AI engine is ready
|
|
233
|
+
* @returns {boolean}
|
|
234
|
+
*/
|
|
235
|
+
isReady() {
|
|
236
|
+
return this.ready;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get the current AI model name
|
|
241
|
+
* @returns {string}
|
|
242
|
+
*/
|
|
243
|
+
getModel() {
|
|
244
|
+
return this.ai.getModel();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Clear the Wikipedia cache
|
|
249
|
+
*/
|
|
250
|
+
clearCache() {
|
|
251
|
+
this.wikipedia.clearCache();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Export everything
|
|
256
|
+
export { DocuFreshAI, WikipediaClient, AIEngine, registerAIMarkers, createAIMarkers };
|
|
257
|
+
export default DocuFreshAI;
|
package/src/markers.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI-Powered Markers
|
|
3
|
+
* Extends docufresh with intelligent content generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create AI marker functions
|
|
8
|
+
* @param {WikipediaClient} wikipedia - Wikipedia client instance
|
|
9
|
+
* @param {AIEngine} ai - AI engine instance
|
|
10
|
+
* @returns {object} - Marker functions
|
|
11
|
+
*/
|
|
12
|
+
function createAIMarkers(wikipedia, ai) {
|
|
13
|
+
return {
|
|
14
|
+
/**
|
|
15
|
+
* Get a fact about a topic from Wikipedia
|
|
16
|
+
* Usage: {{ai_fact:topic_name}}
|
|
17
|
+
* Example: {{ai_fact:Albert_Einstein}} → "Albert Einstein was a German-born theoretical physicist..."
|
|
18
|
+
*/
|
|
19
|
+
ai_fact: async (topic) => {
|
|
20
|
+
const fact = await wikipedia.getFact(topic);
|
|
21
|
+
return fact;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get a short description of a topic
|
|
26
|
+
* Usage: {{ai_describe:topic_name}}
|
|
27
|
+
* Example: {{ai_describe:Python_(programming_language)}} → "General-purpose programming language"
|
|
28
|
+
*/
|
|
29
|
+
ai_describe: async (topic) => {
|
|
30
|
+
const description = await wikipedia.getDescription(topic);
|
|
31
|
+
return description;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Rewrite a sentence incorporating current facts
|
|
36
|
+
* Usage: {{ai_rewrite:topic:template_with_X}}
|
|
37
|
+
* Example: {{ai_rewrite:world_population:The world has X people}}
|
|
38
|
+
* → "The world has approximately 8.1 billion people"
|
|
39
|
+
*/
|
|
40
|
+
ai_rewrite: async (topic, template) => {
|
|
41
|
+
// Get fact from Wikipedia
|
|
42
|
+
const fact = await wikipedia.getFact(topic);
|
|
43
|
+
|
|
44
|
+
// Use AI to rewrite naturally
|
|
45
|
+
const rewritten = await ai.rewriteSentence(fact, template);
|
|
46
|
+
return rewritten;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate a paragraph about a topic
|
|
51
|
+
* Usage: {{ai_paragraph:topic_name}}
|
|
52
|
+
* Example: {{ai_paragraph:SpaceX}} → "SpaceX, founded by Elon Musk..."
|
|
53
|
+
*/
|
|
54
|
+
ai_paragraph: async (topic) => {
|
|
55
|
+
// Get facts from Wikipedia
|
|
56
|
+
const summary = await wikipedia.getSummary(topic);
|
|
57
|
+
|
|
58
|
+
// Use AI to generate a paragraph
|
|
59
|
+
const paragraph = await ai.generateParagraph(summary.title, summary.extract);
|
|
60
|
+
return paragraph;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get a summary of a topic
|
|
65
|
+
* Usage: {{ai_summary:topic_name}}
|
|
66
|
+
* Example: {{ai_summary:Climate_change}} → "Climate change refers to..."
|
|
67
|
+
*/
|
|
68
|
+
ai_summary: async (topic) => {
|
|
69
|
+
const summary = await wikipedia.getSummary(topic);
|
|
70
|
+
|
|
71
|
+
// Use AI to create a concise summary
|
|
72
|
+
const condensed = await ai.summarize(summary.extract);
|
|
73
|
+
return condensed;
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Answer a question using Wikipedia as context
|
|
78
|
+
* Usage: {{ai_answer:topic:question}}
|
|
79
|
+
* Example: {{ai_answer:Moon:How far is it from Earth?}}
|
|
80
|
+
* → "The Moon is approximately 384,400 km from Earth"
|
|
81
|
+
*/
|
|
82
|
+
ai_answer: async (topic, question) => {
|
|
83
|
+
// Get context from Wikipedia
|
|
84
|
+
const summary = await wikipedia.getSummary(topic);
|
|
85
|
+
|
|
86
|
+
// Use AI to answer
|
|
87
|
+
const answer = await ai.answerQuestion(question, summary.extract);
|
|
88
|
+
return answer;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the Wikipedia URL for a topic
|
|
93
|
+
* Usage: {{ai_link:topic_name}}
|
|
94
|
+
* Example: {{ai_link:JavaScript}} → "https://en.wikipedia.org/wiki/JavaScript"
|
|
95
|
+
*/
|
|
96
|
+
ai_link: async (topic) => {
|
|
97
|
+
const summary = await wikipedia.getSummary(topic);
|
|
98
|
+
return summary.url || `https://en.wikipedia.org/wiki/${wikipedia.normalizeTopic(topic)}`;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the last updated date for Wikipedia article
|
|
103
|
+
* Usage: {{ai_updated:topic_name}}
|
|
104
|
+
* Example: {{ai_updated:Bitcoin}} → "2024-01-15"
|
|
105
|
+
*/
|
|
106
|
+
ai_updated: async (topic) => {
|
|
107
|
+
const summary = await wikipedia.getSummary(topic);
|
|
108
|
+
if (summary.timestamp) {
|
|
109
|
+
return new Date(summary.timestamp).toLocaleDateString();
|
|
110
|
+
}
|
|
111
|
+
return 'Unknown';
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Register AI markers with a docufresh instance
|
|
118
|
+
* @param {DocuFresh} docufresh - The docufresh instance
|
|
119
|
+
* @param {WikipediaClient} wikipedia - Wikipedia client
|
|
120
|
+
* @param {AIEngine} ai - AI engine
|
|
121
|
+
*/
|
|
122
|
+
function registerAIMarkers(docufresh, wikipedia, ai) {
|
|
123
|
+
const markers = createAIMarkers(wikipedia, ai);
|
|
124
|
+
|
|
125
|
+
for (const [name, fn] of Object.entries(markers)) {
|
|
126
|
+
docufresh.registerMarker(name, fn);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return markers;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { createAIMarkers, registerAIMarkers };
|
|
133
|
+
export default registerAIMarkers;
|
package/src/wikipedia.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wikipedia API Client
|
|
3
|
+
* Fetches facts and summaries from Wikipedia (no auth required)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class WikipediaClient {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.baseUrl = 'https://en.wikipedia.org/api/rest_v1';
|
|
9
|
+
this.cache = new Map();
|
|
10
|
+
this.cacheTTL = options.cacheTTL || 1000 * 60 * 60; // 1 hour default
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get a summary/fact about a topic from Wikipedia
|
|
15
|
+
* @param {string} topic - The Wikipedia article title (use underscores for spaces)
|
|
16
|
+
* @returns {Promise<object>} - Summary data with extract, title, description
|
|
17
|
+
*/
|
|
18
|
+
async getSummary(topic) {
|
|
19
|
+
const normalizedTopic = this.normalizeTopic(topic);
|
|
20
|
+
|
|
21
|
+
// Check cache first
|
|
22
|
+
const cached = this.getFromCache(normalizedTopic);
|
|
23
|
+
if (cached) return cached;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const url = `${this.baseUrl}/page/summary/${encodeURIComponent(normalizedTopic)}`;
|
|
27
|
+
const response = await this.fetch(url);
|
|
28
|
+
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
throw new Error(`Wikipedia API error: ${response.status}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const data = await response.json();
|
|
34
|
+
|
|
35
|
+
const result = {
|
|
36
|
+
title: data.title,
|
|
37
|
+
description: data.description || '',
|
|
38
|
+
extract: data.extract || '',
|
|
39
|
+
extractShort: data.extract_html ? this.stripHtml(data.extract).slice(0, 200) : '',
|
|
40
|
+
thumbnail: data.thumbnail?.source || null,
|
|
41
|
+
url: data.content_urls?.desktop?.page || null,
|
|
42
|
+
timestamp: data.timestamp || new Date().toISOString()
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Cache the result
|
|
46
|
+
this.setCache(normalizedTopic, result);
|
|
47
|
+
|
|
48
|
+
return result;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`Wikipedia fetch error for "${topic}":`, error.message);
|
|
51
|
+
return {
|
|
52
|
+
title: topic,
|
|
53
|
+
description: '',
|
|
54
|
+
extract: `Unable to fetch information about ${topic}`,
|
|
55
|
+
extractShort: '',
|
|
56
|
+
thumbnail: null,
|
|
57
|
+
url: null,
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
error: true
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get a specific fact/extract from Wikipedia
|
|
66
|
+
* @param {string} topic - The topic to look up
|
|
67
|
+
* @returns {Promise<string>} - The fact/extract text
|
|
68
|
+
*/
|
|
69
|
+
async getFact(topic) {
|
|
70
|
+
const summary = await this.getSummary(topic);
|
|
71
|
+
return summary.extract || `Information about ${topic} not available`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get a short description of a topic
|
|
76
|
+
* @param {string} topic - The topic to look up
|
|
77
|
+
* @returns {Promise<string>} - Short description
|
|
78
|
+
*/
|
|
79
|
+
async getDescription(topic) {
|
|
80
|
+
const summary = await this.getSummary(topic);
|
|
81
|
+
return summary.description || summary.extractShort || topic;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Search Wikipedia for articles matching a query
|
|
86
|
+
* @param {string} query - Search query
|
|
87
|
+
* @param {number} limit - Max results (default 5)
|
|
88
|
+
* @returns {Promise<Array>} - Array of search results
|
|
89
|
+
*/
|
|
90
|
+
async search(query, limit = 5) {
|
|
91
|
+
try {
|
|
92
|
+
const url = `https://en.wikipedia.org/w/api.php?action=opensearch&search=${encodeURIComponent(query)}&limit=${limit}&format=json&origin=*`;
|
|
93
|
+
const response = await this.fetch(url);
|
|
94
|
+
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(`Wikipedia search error: ${response.status}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const data = await response.json();
|
|
100
|
+
// OpenSearch returns [query, titles, descriptions, urls]
|
|
101
|
+
const titles = data[1] || [];
|
|
102
|
+
const descriptions = data[2] || [];
|
|
103
|
+
const urls = data[3] || [];
|
|
104
|
+
|
|
105
|
+
return titles.map((title, i) => ({
|
|
106
|
+
title,
|
|
107
|
+
description: descriptions[i] || '',
|
|
108
|
+
url: urls[i] || ''
|
|
109
|
+
}));
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(`Wikipedia search error for "${query}":`, error.message);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Normalize topic string for Wikipedia API
|
|
118
|
+
*/
|
|
119
|
+
normalizeTopic(topic) {
|
|
120
|
+
return topic
|
|
121
|
+
.trim()
|
|
122
|
+
.replace(/\s+/g, '_') // Spaces to underscores
|
|
123
|
+
.replace(/^_+|_+$/g, ''); // Trim underscores
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Strip HTML tags from text
|
|
128
|
+
*/
|
|
129
|
+
stripHtml(html) {
|
|
130
|
+
return html.replace(/<[^>]*>/g, '');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get from cache if not expired
|
|
135
|
+
*/
|
|
136
|
+
getFromCache(key) {
|
|
137
|
+
const item = this.cache.get(key);
|
|
138
|
+
if (!item) return null;
|
|
139
|
+
|
|
140
|
+
if (Date.now() - item.timestamp > this.cacheTTL) {
|
|
141
|
+
this.cache.delete(key);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return item.data;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Set cache item
|
|
150
|
+
*/
|
|
151
|
+
setCache(key, data) {
|
|
152
|
+
this.cache.set(key, {
|
|
153
|
+
data,
|
|
154
|
+
timestamp: Date.now()
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Clear the cache
|
|
160
|
+
*/
|
|
161
|
+
clearCache() {
|
|
162
|
+
this.cache.clear();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Fetch wrapper - works in both Node.js and browser
|
|
167
|
+
*/
|
|
168
|
+
async fetch(url) {
|
|
169
|
+
// Use global fetch (available in Node 18+ and browsers)
|
|
170
|
+
if (typeof fetch !== 'undefined') {
|
|
171
|
+
return fetch(url, {
|
|
172
|
+
headers: {
|
|
173
|
+
'User-Agent': 'DocuFresh-AI/0.1.0 (https://github.com/manthenavamsi/docufresh-ai)',
|
|
174
|
+
'Accept': 'application/json'
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Fallback for older Node.js (shouldn't be needed with Node 18+)
|
|
180
|
+
throw new Error('fetch is not available. Please use Node.js 18+ or a browser environment.');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export { WikipediaClient };
|
|
185
|
+
export default WikipediaClient;
|