Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/review.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ jobs:
run: |
docker compose --profile lint run drupal-check
env:
TARGET_DRUPAL_CORE_VERSION: 11
DRUPAL_RECOMMENDED_PROJECT: 11.0.0
42 changes: 32 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ generating, modifying, and enhancing content directly within your editor.
- PHP 8.1 or higher
- Key module (for secure API key storage)
- Markdownify module (provides markdown versions of Drupal content for AI to access when URLs are included in prompts)
- OpenAI API key
- [AI module](https://www.drupal.org/project/ai) (recommended) — routes AI requests through Drupal server-side, keeping API keys out of the browser. Works with any AI provider plugin (DXPR, OpenAI, Anthropic, Ollama, and more).
- An AI provider module (e.g. [DXPR AI Provider](https://www.drupal.org/project/ai_provider_dxpr), [OpenAI](https://www.drupal.org/project/ai_provider_openai), etc.) or a direct API key

## Installation

Expand All @@ -23,14 +24,33 @@ generating, modifying, and enhancing content directly within your editor.
drush en ckeditor_ai_agent
```

2. **Configure API Key Storage**
2. **Set Up an AI Provider (recommended)**

Install the AI module and a provider plugin to route requests server-side
(API keys stay on the server, never sent to the browser):
```bash
composer require drupal/ai drupal/ai_provider_dxpr
drush en ai ai_provider_dxpr
```
Then configure a default Chat provider at **Administration > Configuration >
AI > Settings** (`/admin/config/ai/settings`).

Any AI provider supported by the AI module will work (DXPR, OpenAI,
Anthropic, Ollama, etc.).

Alternatively, you can use the module without the AI module by configuring
an API key directly (see step 3).

3. **Configure API Key Storage (without AI module)**

If not using the AI module, configure direct API access:
- Go to **Administration > Configuration > System > Keys**
(`admin/config/system/keys`)
- Add a new key for your OpenAI API credentials
- Add a new key for your AI provider API credentials
- Select "Authentication" as the key type
- Enter your OpenAI API key value
- Enter your API key value

3. **Configure CKEditor Integration**
4. **Configure CKEditor Integration**
- Go to **Administration > Configuration > Content authoring > Text formats
and editors** (`admin/config/content/formats`)
- Edit your desired text format (typically Full HTML)
Expand Down Expand Up @@ -87,9 +107,9 @@ settings.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| **Basic Settings** ||||
| `key_provider` | `string` | - | Select the key that contains your OpenAI API credentials |
| `model` | `string` | `'gpt-4o'` | Select AI model: GPT-4o (Most capable), GPT-4o Mini (Balanced), or GPT-3.5 Turbo (Fastest) |
| `endpointUrl` | `string` | - | OpenAI API endpoint URL. Only change if using a custom endpoint or proxy |
| `key_provider` | `string` | - | Select the key that contains your AI provider API credentials. Not needed when using the AI module (keys are managed server-side). |
| `model` | `string` | `'gpt-4o'` | AI model to use. When the AI module is installed, the default provider's model is used automatically. |
| `endpointUrl` | `string` | - | API endpoint URL. Automatically set when using the AI module. Only change if using a custom endpoint or direct API access. |
| **Advanced Settings** ||||
| `temperature` | `number` | `0.7` | Controls the creativity of AI responses. Low values (0.0-0.5) produce consistent, deterministic responses ideal for factual content. Medium values (0.6-1.0) offer balanced creativity. High values (1.1-2.0) generate more diverse and unexpected responses |
| `maxOutputTokens` | `number` | Model's max limit | Maximum number of tokens for AI response. If not set, uses model's maximum limit |
Expand All @@ -111,6 +131,8 @@ settings.

- **Slash Command Integration**: Type "/" to trigger AI commands
- **Real-time Content Generation**: See AI-generated content as it's created
- **Secure Server-Side Proxy**: AI requests route through Drupal via the AI module — API keys never reach the browser
- **400+ AI Models**: Works with any provider supported by the Drupal AI module (DXPR, OpenAI, Anthropic, Ollama, and more)
- **Context-Aware Responses**: AI considers surrounding content
- **Content Moderation**: Optional content filtering
- **Multiple Language Support**: Works with CKEditor's language settings
Expand Down Expand Up @@ -143,8 +165,8 @@ settings.

## Permissions

To manage access to CKEditor AI Agent settings, grant the following permission:
- "Administer CKEditor AI Agent"
- **"Administer CKEditor AI Agent"** — Access to global settings at `/admin/config/content/ckeditor-ai-agent`
- **"Use CKEditor AI Agent"** — Required for sending AI requests through the server-side proxy endpoint. Grant this to roles that should have AI access in the editor.

## Troubleshooting

Expand Down
1 change: 1 addition & 0 deletions ckeditor_ai_agent.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ description: 'Adds AI-powered content generation capabilities to CKEditor 5.'
package: CKEditor
core_version_requirement: ^10.3 || ^11
dependencies:
- ai:ai
- drupal:ckeditor5
- key:key
- markdownify:markdownify
Expand Down
4 changes: 4 additions & 0 deletions ckeditor_ai_agent.permissions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ administer ckeditor ai agent:
title: 'Administer CKEditor AI Agent'
description: 'Configure CKEditor AI Agent settings.'
restrict access: true

use ckeditor ai agent:
title: 'Use CKEditor AI Agent'
description: 'Send AI requests through the CKEditor AI Agent proxy endpoint.'
9 changes: 9 additions & 0 deletions ckeditor_ai_agent.routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@ ckeditor_ai_agent.settings:
_title: 'CKEditor AI Agent Settings'
requirements:
_permission: 'administer ckeditor ai agent'

ckeditor_ai_agent.ai_chat:
path: '/api/ckeditor-ai-agent/ai/chat'
methods: [POST]
defaults:
_controller: '\Drupal\ckeditor_ai_agent\Controller\AiChatController::chat'
requirements:
_permission: 'use ckeditor ai agent'
_csrf_token: 'TRUE'
5 changes: 4 additions & 1 deletion ckeditor_ai_agent.services.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
services:
ckeditor_ai_agent.configuration_manager:
class: Drupal\ckeditor_ai_agent\AiAgentConfigurationManager
arguments: ['@config.factory', '@ckeditor_ai_agent.key_service', '@entity_type.manager', '@logger.channel.ckeditor_ai_agent']
arguments: ['@config.factory', '@ckeditor_ai_agent.key_service', '@entity_type.manager', '@logger.channel.ckeditor_ai_agent', '@module_handler', '@csrf_token']
logger.channel.ckeditor_ai_agent:
parent: logger.channel_base
arguments: ['ckeditor_ai_agent']
ckeditor_ai_agent.key_service:
class: Drupal\ckeditor_ai_agent\Service\AiAgentKeyService
arguments: ['@config.factory', '@module_handler', '@entity_type.manager', '@?key.repository']
ckeditor_ai_agent.ai_chat_provider_gateway:
class: Drupal\ckeditor_ai_agent\Service\AiChatProviderGateway
arguments: ['@ai.provider']
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
"license": "GPL-2.0+",
"minimum-stability": "dev",
"require": {
"drupal/ai": "^1.2",
"drupal/core": "^10.3 || ^11",
"drupal/key": "^1.16",
"drupal/markdownify": "^1"
},
"suggest": {
"drupal/ai_provider_dxpr": "^1.0@alpha - DXPR AI provider for the ai module"
}
}
11 changes: 7 additions & 4 deletions docs/ckeditor_ai_agent_desc.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ <h3 id="module-project--features">Features</h3>
<li><strong>Web content integration:</strong> Includes content from user-provided URLs directly into generated text</li>
<li><strong>Context-aware writing:</strong> Automatically uses surrounding editor content to enhance response accuracy</li>
<li><strong>Selection-based edits:</strong> Select text to quickly revise with AI</li>
<li><strong>400+ AI models support:</strong> Works with commercial and open source AI vendors</li>
<li><strong>400+ AI models support:</strong> Works with commercial and open source AI vendors via the <a href="https://www.drupal.org/project/ai">Drupal AI module</a></li>
<li><strong>Secure server-side proxy:</strong> AI requests are routed through Drupal — API keys are never exposed to the browser</li>
<li><strong>Smart formatting:</strong> Automatically ensures generated HTML matches your editor's allowed content</li>
<li><strong>Multilingual support:</strong> Fully supports Drupal's multilingual content features</li>
<li><strong>Simple menu options:</strong> Easily edit, review, fix errors, and adjust tone from dropdown menus</li>
Expand All @@ -36,9 +37,10 @@ <h3 id="module-project--keyboard-shortcuts">Keyboard Shortcuts</h3>

<h3 id="module-project--post-installation">Post-installation</h3>
<ol>
<li>Configure AI provider settings at /admin/config/ai/providers</li>
<li>Configure global AI settings at /admin/config/content/ckeditor-ai-agent</li>
<li>Configure permissions at /admin/people/permissions</li>
<li>Install and enable the <a href="https://www.drupal.org/project/ai">AI module</a> and an AI provider module (e.g. <a href="https://www.drupal.org/project/ai_provider_dxpr">DXPR AI Provider</a>, <a href="https://www.drupal.org/project/ai_provider_openai">OpenAI</a>, or any other supported provider)</li>
<li>Configure a default Chat provider at /admin/config/ai/settings</li>
<li>Configure global AI Agent settings at /admin/config/content/ckeditor-ai-agent</li>
<li>Grant the "Use CKEditor AI Agent" permission to roles that should have AI access</li>
<li>Set up text formats at /admin/config/content/formats</li>
<li>Add the AI Agent button to your CKEditor toolbars</li>
<li>Start using slash commands or the toolbar button for AI assistance</li>
Expand Down Expand Up @@ -76,5 +78,6 @@ <h3 id="module-project--usage-examples">Usage Examples</h3>
<h3>Dependencies</h3>
<ul>
<li><strong><a href="https://www.drupal.org/project/markdownify">Markdownify module</a></strong> (<code>drupal/markdownify</code>) - Provides markdown versions of Drupal content that the AI can access when URLs are included in prompts. Essential for referencing draft articles, internal documentation, or any unpublished content while respecting user permissions. For example, enables workflows like generating social posts for draft articles.</li>
<li><strong><a href="https://www.drupal.org/project/ai">AI module</a></strong> (<code>drupal/ai</code>) - Recommended. Routes AI requests through Drupal server-side, keeping API keys secure. Works with any AI provider plugin (DXPR, OpenAI, Anthropic, Ollama, and more).</li>
</ul>

2 changes: 1 addition & 1 deletion js/build/ai-agent.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions js/ckeditor5_plugins/aiagent/src/aiagent.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export default class AiAgent extends Plugin {
constructor(editor) {
super(editor);
const config = editor.config.get('aiAgent') || {};
// Check if plugin is enabled based on presence of API key
this.isEnabled = Boolean(config.apiKey);
// Enable when either direct API auth or a proxy endpoint is configured.
this.isEnabled = Boolean(config.apiKey || config.endpointUrl);
// Set default values and merge with provided config
const defaultConfig = {
engine: this.DEFAULT_GPT_ENGINE, // Default AI model
Expand Down
46 changes: 32 additions & 14 deletions scripts/linters/prepare-drupal-lint.sh
Original file line number Diff line number Diff line change
@@ -1,27 +1,45 @@
#!/bin/bash

echo "$COMPOSER_HOME: $COMPOSER_HOME"

# Allow the plugin installer
composer global config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true

# Install Drupal coding standards
composer global require drupal/coder
if [ -z "$TARGET_DRUPAL_CORE_VERSION" ]; then
TARGET_DRUPAL_CORE_VERSION=11
fi

# Install PHP compatibility checker
composer global require phpcompatibility/php-compatibility
echo "$COMPOSER_HOME: $COMPOSER_HOME"
echo "TARGET_DRUPAL_CORE_VERSION: $TARGET_DRUPAL_CORE_VERSION"

# Ensure COMPOSER_HOME directory exists.
mkdir -p "$COMPOSER_HOME"

# Create/overwrite global composer.json to trust required plugins up-front.
echo '{
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"drupal/coder": true,
"phpcompatibility/php-compatibility": true
}
}
}' > "$COMPOSER_HOME/composer.json"

# Install PHPCS tooling with a PHPCompatibility-compatible phpcs major version.
composer global require \
drupal/coder \
phpcompatibility/php-compatibility \
squizlabs/php_codesniffer:^3 \
dealerdirect/phpcodesniffer-composer-installer \
--with-all-dependencies

export PATH="$PATH:$COMPOSER_HOME/vendor/bin"

# Install PHPCS plugin installer
composer global require dealerdirect/phpcodesniffer-composer-installer

composer global show -P
phpcs -i

# Ensure PHPCompatibility is always discoverable even if plugin hooks are skipped.
phpcs --config-set installed_paths "$COMPOSER_HOME/vendor/drupal/coder/coder_sniffer,$COMPOSER_HOME/vendor/phpcompatibility/php-compatibility,$COMPOSER_HOME/vendor/sirbrillig/phpcs-variable-analysis,$COMPOSER_HOME/vendor/slevomat/coding-standard"

# Configure PHPCS settings
phpcs --config-set colors 1
phpcs --config-set ignore_warnings_on_exit 1
phpcs --config-set drupal_core_version 11
phpcs --config-set drupal_core_version "$TARGET_DRUPAL_CORE_VERSION"

phpcs --config-show
phpcs --config-show
17 changes: 14 additions & 3 deletions scripts/linters/run-drupal-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

set -vo pipefail

if [ -z "$DRUPAL_RECOMMENDED_PROJECT" ]; then
# Pin to a concrete Drupal 11 release for deterministic CI behavior.
DRUPAL_RECOMMENDED_PROJECT=11.0.0
fi

# Install required libs for Drupal
GD_ENABLED=$(php -i | grep 'GD Support' | awk '{ print $4 }')

Expand All @@ -12,18 +17,21 @@ fi

# Create project in a temporary directory inside the container
INSTALL_DIR="/drupal_install_tmp"
composer create-project drupal/recommended-project:11.x-dev "$INSTALL_DIR" --no-interaction --stability=dev
composer create-project drupal/recommended-project=$DRUPAL_RECOMMENDED_PROJECT "$INSTALL_DIR" --no-interaction

cd "$INSTALL_DIR"

# Allow specific plugins needed by dependencies before requiring them.
composer config --no-plugins allow-plugins.tbachert/spi true --no-interaction
composer config --no-plugins allow-plugins.phpstan/extension-installer true --no-interaction

# Create phpstan.neon config file
cat <<EOF > phpstan.neon
parameters:
paths:
- web/modules/contrib/ckeditor_ai_agent
scanDirectories:
- web/modules/contrib/ai
excludePaths:
- web/modules/contrib/ckeditor_ai_agent/node_modules (?)
# Set the analysis level (0-9)
Expand All @@ -44,8 +52,11 @@ if ! composer require drupal/markdownify; then
echo "Warning: Could not install drupal/markdownify (optional dependency)"
fi

# Install AI module classes used by this module's proxy controller.
composer require 'drupal/ai:^1.2' --no-interaction

# Install PHPStan extensions for Drupal 11 and Drush for command analysis
composer require --dev phpstan/phpstan mglaman/phpstan-drupal phpstan/phpstan-deprecation-rules drush/drush --with-all-dependencies --no-interaction
composer require --dev phpstan/extension-installer:^1.4 phpstan/phpstan:^2.1.42 mglaman/phpstan-drupal:^2.0.11 phpstan/phpstan-deprecation-rules:^2.0 drush/drush:^13.7 --with-all-dependencies --no-interaction

# Run phpstan
./vendor/bin/phpstan analyse --memory-limit=-1 -c phpstan.neon
./vendor/bin/phpstan analyse --memory-limit=-1 -c phpstan.neon
Loading
Loading