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 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
+ [![npm version](https://img.shields.io/npm/v/docufresh-ai.svg)](https://www.npmjs.com/package/docufresh-ai)
4
+ [![license](https://img.shields.io/npm/l/docufresh-ai.svg)](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
+ }
@@ -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;
@@ -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;