@wout_f/aindredacteur
v1.9.6
Published
DSP Tools - AI-powered editing tools for Tweakers DSP
Readme
aindredacteur Package
A standalone JavaScript package for AI-powered text editing, designed for the Tweakers DSP but usable in any CMS environment.
The package is fully autonomous - it handles CMS detection, layout modifications, field discovery, and UI creation automatically. No DOM manipulation required from your side.
Installation
npm install aindredacteur-packageQuick Start
Simple Integration (Recommended)
The package handles everything automatically - just call init() with your API endpoints:
import { Aindredacteur } from 'aindredacteur-package';
const aiPackage = new Aindredacteur();
await aiPackage.init({
api: {
endpoints: {
proofreader: 'https://your-api/proofreader',
titleSuggester: 'https://your-api/title-suggester',
feedback: 'https://your-api/feedback'
},
apiKey: 'your-api-key'
},
options: {
autoRegister: true, // Auto-detect and register CMS fields (default)
debug: false
}
});
// That's it! The package will:
// 1. Detect your CMS type automatically
// 2. Apply necessary layout modifications
// 3. Discover and register all relevant fields
// 4. Create and display AI panelsWhat the Package Does Automatically
When you call init(), the package:
- Detects CMS Type: Uses built-in detection to identify your CMS (News, Review, Geek, etc.)
- Applies Layout: Injects CSS to create side-by-side layout for editors and AI panels
- Prepares Containers: Enhances field containers with appropriate classes and ARIA attributes
- Registers Fields: Discovers and registers all CMS fields automatically
- Creates UI: Generates AI panels and suggestion buttons in the correct positions
No manual DOM manipulation needed - the package is fully self-contained.
Configuration
API Configuration
{
api: {
endpoints: {
proofreader: 'https://...', // Text analysis endpoint
titleSuggester: 'https://...', // Title suggestions endpoint
feedback: 'https://...' // Content feedback endpoint
},
// Single API key for all endpoints
apiKey: 'your-key',
// Or per-endpoint keys
apiKeys: {
proofreader: 'key1',
titleSuggester: 'key2',
feedback: 'key3'
}
}
}Options
{
options: {
autoRegister: true, // Auto-detect and register CMS fields (default: true)
debug: false, // Enable debug logging
useV2Pipeline: true // Use V2 suggestion pipeline (recommended)
}
}autoRegister Option
The autoRegister option controls automatic field detection and registration:
autoRegister: true (Default - Recommended)
await aiPackage.init({
api: { endpoints: { /* ... */ } },
options: { autoRegister: true }
});
// Package automatically:
// - Detects CMS type
// - Applies layout modifications
// - Discovers all fields
// - Registers fields with StateManager
// - Creates AI panelsautoRegister: false (Manual Control)
await aiPackage.init({
api: { endpoints: { /* ... */ } },
options: { autoRegister: false }
});
// Package still detects CMS and applies layout, but you must register fields manually:
const fieldElement = document.querySelector('#my_field');
const containerElement = document.querySelector('#my_container');
aiPackage.addField(fieldElement, containerElement, 'articleBody');Use autoRegister: false only if you need custom field registration logic or want to control which fields are registered.
Legacy Configuration (Deprecated)
The old configuration format is still supported but deprecated:
// Old format (deprecated)
await aiPackage.init({
apiEndpoints: {
proofReader: 'https://...',
titleSuggester: 'https://...',
feedback: 'https://...'
},
apiKey: 'your-key'
});Migration Guide
Migrating from Manual DOM Manipulation
Before (Old Pattern - Deprecated)
// 1. Manually detect CMS type
const cmsType = detectCMSType();
// 2. Manually apply layout modifications
applyLayoutCSS(cmsType);
createFieldContainers();
// 3. Initialize package
const aiPackage = new Aindredacteur();
await aiPackage.init({
apiEndpoints: { /* ... */ }
});
// 4. Manually register each field
const bodyField = document.querySelector('#news_form_body');
const bodyContainer = document.querySelector('#row_news_form_body .field');
aiPackage.addField(bodyField, bodyContainer, 'articleBody');
const titleField = document.querySelector('#news_form_name');
const titleContainer = document.querySelector('#row_news_form_name .field');
aiPackage.addField(titleField, titleContainer, 'title');After (New Pattern - Recommended)
// Just initialize - package handles everything
const aiPackage = new Aindredacteur();
await aiPackage.init({
api: {
endpoints: {
proofreader: 'https://...',
titleSuggester: 'https://...',
feedback: 'https://...'
},
apiKey: 'your-key'
},
options: {
autoRegister: true // Default, can be omitted
}
});
// That's it! No manual DOM manipulation needed.Migrating Chrome Extensions
Before (Old Extension Pattern)
// extension/content.js
import { ChromeExtensionAdapter } from './chrome-extension-adapter.js';
import { Aindredacteur } from './aindredacteur-package/index.js';
// 1. Create adapter
const adapter = new ChromeExtensionAdapter();
// 2. Detect CMS and apply layout
await adapter.detectAndSetupCMS();
// 3. Initialize package
const aiPackage = new Aindredacteur();
await aiPackage.init({ apiEndpoints: { /* ... */ } });
// 4. Register fields via adapter
adapter.registerFieldsWithAI(aiPackage);After (New Extension Pattern)
// extension/content.js
import { Aindredacteur } from './aindredacteur-package/index.js';
// Just initialize - package handles CMS detection, layout, and field registration
const aiPackage = new Aindredacteur();
await aiPackage.init({
api: {
endpoints: {
proofreader: chrome.runtime.getURL('api/proofreader'),
titleSuggester: chrome.runtime.getURL('api/title-suggester'),
feedback: chrome.runtime.getURL('api/feedback')
}
},
options: {
autoRegister: true
}
});
// ChromeExtensionAdapter is no longer neededKey Changes
- No Manual CMS Detection: Package detects CMS automatically using
CMSRegistry - No Manual Layout: Package applies layout CSS automatically using
LayoutManager - No Manual Field Registration: Package discovers and registers fields automatically
- Simplified Configuration: Just provide API endpoints, package handles the rest
- Cleaner Code: Reduced from ~100 lines to ~10 lines for typical integration
Custom Transport Layer
For advanced use cases, you can provide a custom transport:
import { Aindredacteur } from 'aindredacteur-package';
import { FetchTransport, MockTransport } from 'aindredacteur-package/core/transport.js';
// Custom fetch transport
const transport = new FetchTransport({
apiKey: 'your-key',
timeout: 30000
});
await aiPackage.init({
transport,
api: {
endpoints: { /* ... */ }
}
});
// Mock transport for testing
const mockTransport = new MockTransport({
'proofreader': { changes: [], flags: {} }
});
await aiPackage.init({ transport: mockTransport });Architecture
How It Works
The package is fully autonomous and handles all DOM manipulation internally:
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
│ │
│ await aiPackage.init({ api: { endpoints: {...} } }) │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Aindredacteur Package │
│ │
│ ┌────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ CMSRegistry │ │ LayoutManager │ │ StateManager │ │
│ │ │ │ │ │ │ │
│ │ • Detect CMS │ │ • Apply CSS │ │ • Track │ │
│ │ • Get config │ │ • Prepare │ │ fields │ │
│ │ │ │ containers │ │ • Manage UI │ │
│ └────────────────┘ └─────────────────┘ └──────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Auto-Registration │ │
│ │ • Discover fields from CMS config │ │
│ │ • Register with StateManager │ │
│ │ • Create AI panels │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘Key Components
CMSRegistry: Detects CMS type by checking DOM structure against known patterns. Returns configuration with field selectors and layout dimensions.
LayoutManager: Applies CMS-specific CSS modifications to create side-by-side layout. Prepares field containers with appropriate classes and ARIA attributes.
StateManager: Tracks registered fields, manages UI panels, and coordinates suggestion application.
Auto-Registration: Discovers fields based on CMS configuration and automatically registers them with the StateManager.
Initialization Flow
- CMS Detection:
CMSRegistry.detect()identifies CMS type - Layout Application:
LayoutManager.applyLayout()injects CSS - Container Preparation:
LayoutManager.prepareContainers()enhances containers - Field Discovery: Iterate over fields from CMS configuration
- Field Registration: Register each field with StateManager
- UI Creation: Create AI panels and suggestion buttons
All of this happens automatically when you call init() with autoRegister: true.
Supported CMS Types
The package auto-detects these CMS types and applies appropriate layout modifications:
| CMS Type | Detection Selector | Auto-Registered Fields |
|----------|-------------------|------------------------|
| News | #row_news_form_body .field | Article Body, Title, Introduction |
| Geek | #row_geek_form_body .field | Article Body, Title, Introduction |
| Review | #row_review_page_form_body | Chapter Body, Title |
| Review Snippet | #review_form_articleSnippet_snippetText | Snippet Text |
| Plan | #row_plan_form_body .field | Article Body, Title, Introduction |
| Product Card | #row_product_card_form_description .field | Description |
| Scorecard | textarea#scorecard_form_bottomline | Bottom Line |
When a CMS is detected, the package automatically:
- Applies CMS-specific layout CSS (page width, grid layout, styling)
- Prepares field containers with appropriate classes and ARIA attributes
- Discovers and registers all fields defined in the CMS configuration
- Creates AI panels in the correct positions
If no CMS is detected, the package logs a warning and continues without layout modifications. You can still use manual field registration in this case.
Troubleshooting
No CMS Detected
If you see the warning [Aindredacteur] No CMS detected, skipping layout modifications:
- Check DOM Structure: Ensure your page has the expected CMS selectors
- Manual Registration: Use
autoRegister: falseand register fields manually - Custom CMS: Add your CMS to
CMSRegistryif it's not supported
// Manual registration when CMS not detected
await aiPackage.init({
api: { endpoints: { /* ... */ } },
options: { autoRegister: false }
});
const field = document.querySelector('#my_custom_field');
const container = document.querySelector('#my_custom_container');
aiPackage.addField(field, container, 'articleBody');Layout Not Applied
If the layout doesn't appear correctly:
- Check Console: Look for error messages in browser console
- Verify CMS Detection: Check
aiPackage.cmsTypeto confirm detection - Inspect CSS: Verify layout CSS was injected (check
<head>for<style>tags) - Check Conflicts: Look for CSS conflicts with existing page styles
Fields Not Registered
If fields aren't being registered automatically:
- Enable Debug Mode: Set
options.debug: trueto see detailed logs - Check Selectors: Verify field elements exist in DOM when
init()is called - Timing Issues: Ensure
init()is called after DOM is ready - Manual Registration: Fall back to manual registration if needed
// Debug mode for troubleshooting
await aiPackage.init({
api: { endpoints: { /* ... */ } },
options: {
autoRegister: true,
debug: true // Enables detailed logging
}
});Chrome Extension Context
If loading in a Chrome extension:
- Resource Paths: Package automatically detects Chrome extension context
- Web Accessible Resources: Ensure package files are listed in manifest
- CSP Issues: Check Content Security Policy allows inline styles
// manifest.json
{
"web_accessible_resources": [{
"resources": [
"aindredacteur-package/*",
"aindredacteur-package/**/*"
],
"matches": ["<all_urls>"]
}]
}API
Aindredacteur
Main class for the AI editing package.
Constructor
const ai = new Aindredacteur();Methods
init(config)
Initialize the package with API configuration.
await ai.init({
api: {
endpoints: {
proofreader: 'https://...',
titleSuggester: 'https://...',
feedback: 'https://...'
},
apiKey: 'your-key'
},
options: {
autoRegister: true, // Auto-detect and register fields (default: true)
debug: false, // Enable debug logging
useV2Pipeline: true // Use V2 suggestion pipeline (recommended)
}
});When autoRegister: true (default), the package automatically:
- Detects CMS type using
CMSRegistry - Applies layout modifications using
LayoutManager - Prepares field containers
- Discovers and registers all CMS fields
addField(element, container, fieldType)
Manually register a field (only needed when autoRegister: false).
const fieldElement = document.querySelector('#my_field');
const containerElement = document.querySelector('#my_container');
const fieldId = ai.addField(fieldElement, containerElement, 'articleBody');Parameters:
element: The editor element (textarea, input, or CKEditor instance)container: The container element where AI panels will be createdfieldType: Field type ('articleBody', 'title', 'introduction', etc.)
Returns: Field ID string
runProofreader(fieldId)
Run proofreader analysis on a specific field.
await ai.runProofreader(fieldId);getTitleSuggestions(articleText)
Get AI-generated title suggestions.
const suggestions = await ai.getTitleSuggestions(articleText);getFeedback(introText, articleText)
Get content quality feedback.
const feedback = await ai.getFeedback(introText, articleText);destroy()
Clean up and remove all package modifications.
ai.destroy();This removes:
- All injected CSS styles
- All added body classes
- All AI panel elements
- All field container enhancements
- All event listeners
The page is restored to its pre-initialization state.
Properties
cmsType
The detected CMS type (e.g., 'news', 'review', 'scorecard').
console.log(ai.cmsType); // 'news'cmsName
The human-readable CMS name.
console.log(ai.cmsName); // 'News CMS'detectedCMS
The full CMS configuration object from CMSRegistry.
console.log(ai.detectedCMS);
// {
// type: 'news',
// name: 'News CMS',
// fields: { ... },
// layout: { ... }
// }Testing
Unit Tests
Run package unit tests with Vitest:
cd aindredacteur-package
npx vitest runIntegration Tests
Test the package in a real browser environment:
# Start development server
npm run dev
# Open test page
# Navigate to http://localhost:8001/dev-tools/test-page.htmlMock Transport for Testing
Use MockTransport to test without real API calls:
import { Aindredacteur } from 'aindredacteur-package';
import { MockTransport } from 'aindredacteur-package/core/transport.js';
const mockTransport = new MockTransport({
'proofreader': {
changes: [
{ type: 'spelling', original: 'teh', suggestion: 'the', offset: 0 }
],
flags: { hasSpellingErrors: true }
},
'titleSuggester': {
suggestions: ['Great Title 1', 'Great Title 2']
}
});
const aiPackage = new Aindredacteur();
await aiPackage.init({
transport: mockTransport,
options: { autoRegister: false }
});Examples
Basic CMS Integration
import { Aindredacteur } from 'aindredacteur-package';
// Initialize on page load
document.addEventListener('DOMContentLoaded', async () => {
const aiPackage = new Aindredacteur();
await aiPackage.init({
api: {
endpoints: {
proofreader: 'https://api.example.com/proofreader',
titleSuggester: 'https://api.example.com/title-suggester',
feedback: 'https://api.example.com/feedback'
},
apiKey: 'your-api-key'
},
options: {
autoRegister: true,
debug: true
}
});
console.log(`Initialized for CMS: ${aiPackage.cmsName}`);
});Chrome Extension Integration
// content-script.js
import { Aindredacteur } from './aindredacteur-package/index.js';
async function initializeExtension() {
const aiPackage = new Aindredacteur();
await aiPackage.init({
api: {
endpoints: {
proofreader: chrome.runtime.getURL('api/proofreader'),
titleSuggester: chrome.runtime.getURL('api/title-suggester'),
feedback: chrome.runtime.getURL('api/feedback')
}
},
options: {
autoRegister: true
}
});
// Make available for debugging
window.aiPackage = aiPackage;
}
// Wait for CKEditor if present
if (window.CKEDITOR?.instances) {
initializeExtension();
} else if (window.CKEDITOR) {
CKEDITOR.on('instanceReady', initializeExtension);
} else {
initializeExtension();
}Manual Field Registration
import { Aindredacteur } from 'aindredacteur-package';
const aiPackage = new Aindredacteur();
await aiPackage.init({
api: { endpoints: { /* ... */ } },
options: { autoRegister: false }
});
// Register custom fields
const bodyField = document.querySelector('#custom_body_field');
const bodyContainer = document.querySelector('#custom_body_container');
const bodyFieldId = aiPackage.addField(bodyField, bodyContainer, 'articleBody');
const titleField = document.querySelector('#custom_title_field');
const titleContainer = document.querySelector('#custom_title_container');
const titleFieldId = aiPackage.addField(titleField, titleContainer, 'title');
// Run proofreader on specific field
await aiPackage.runProofreader(bodyFieldId);With CKEditor
import { Aindredacteur } from 'aindredacteur-package';
// Wait for CKEditor to be ready
CKEDITOR.on('instanceReady', async (event) => {
const aiPackage = new Aindredacteur();
await aiPackage.init({
api: { endpoints: { /* ... */ } },
options: { autoRegister: true }
});
// Package automatically detects and registers CKEditor instances
console.log('AI package initialized with CKEditor support');
});Contributing
Development Setup
# Install dependencies
npm install
# Run tests
npx vitest run
# Run tests in watch mode
npx vitest
# Run tests with coverage
npx vitest run --coverageAdding New CMS Support
To add support for a new CMS type:
- Add CMS Configuration to CMSRegistry (
core/cms-registry.js):
{
type: 'my-cms',
name: 'My CMS',
detector: () => {
return document.querySelector('#my-cms-identifier') !== null;
},
fields: {
articleBody: {
container: '#my-cms-body-container',
editor: '#my-cms-body-editor',
editorType: 'ckeditor'
},
title: {
container: '#my-cms-title-container',
input: '#my-cms-title-input',
editorType: 'input'
}
},
layout: {
pageWidth: '1280px',
contentWidth: '1260px',
gridColumns: '724px 380px',
ckeMaxWidth: '704px'
}
}- Test Detection: Verify CMS is detected correctly
- Test Layout: Verify layout modifications work correctly
- Test Auto-Registration: Verify fields are registered automatically
License
UNLICENSED
