Skip to content

Commit 5d8d7c5

Browse files
Release 1.2.5
Resolves #25, #36, #38, #39
1 parent 76a34e6 commit 5d8d7c5

30 files changed

+762
-781
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## 1.2.5 - 2025-04-10
6+
### Added
7+
- Added GPT-4.5 Preview to the available models for OpenAI.
8+
- Added Grok 3 & 2 to the available models for x.AI.
9+
- Allow generating entries related to the current entry's fields. [Issue #25](https://github.com/convergine/craft-content-buddy/issues/25)
10+
11+
### Changed
12+
- Show a link to the translated pages in the translation overview.
13+
14+
### Fixed
15+
- Buddy button not rendering on dynamically added CKEditor fields. [Issue #36](https://github.com/convergine/craft-content-buddy/issues/36)
16+
- Issue with migrations not being applied after updates. [Issue #38](https://github.com/convergine/craft-content-buddy/issues/38)
17+
- Save drafts before translating, and create matrix fields when translating entries that don't exist yet. [Issue #39](https://github.com/convergine/craft-content-buddy/issues/39)
18+
- Issue with large content translations cut in half when using gpt-3.5-turbo and gpt-4-turbo models.
19+
- Make sure MatrixBlock translations are only processed in Craft CMS 4.x.
20+
- Issue with translations removing some HTML tags.
21+
522
## 1.2.4 - 2025-03-19
623
### Changed
724
- Allow selecting different API versions for DeepL.

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "convergine/craft-content-buddy",
33
"description": "Content Buddy is an AI-driven CraftCMS plugin that leverages the ChatGPT API to automatically generate and manage multi-language content, including text and images, with customizable settings and features for precise control and enhanced content creation.",
44
"type": "craft-plugin",
5-
"version": "1.2.4",
5+
"version": "1.2.5",
66
"keywords": [
77
"craftcms",
88
"content-buddy",

src/BuddyPlugin.php

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use convergine\contentbuddy\models\SettingsModel;
55
use convergine\contentbuddy\services\Base;
66
use convergine\contentbuddy\services\ContentGenerator;
7+
use convergine\contentbuddy\services\GenerateEntry;
78
use convergine\contentbuddy\services\Prompt;
89
use convergine\contentbuddy\services\PromptProcessor;
910
use convergine\contentbuddy\services\Request;
@@ -27,19 +28,20 @@
2728
use yii\base\Event;
2829

2930
/**
30-
* @property Prompt $promptService;
31+
* @property Prompt $promptService;
3132
* @property ContentGenerator $contentGenerator;
3233
* @property Request $request;
3334
* @property PromptProcessor $promptProcessor;
3435
* @property Translate $translate;
36+
* @property GenerateEntry $generateEntry;
3537
* @property Base $base;
3638
*/
37-
class BuddyPlugin extends Plugin
38-
{
39-
public static $plugin;
39+
class BuddyPlugin extends Plugin {
40+
public static string $plugin;
4041
public ?string $name = 'Content Buddy';
42+
public string $schemaVersion = '1.2.5';
4143

42-
public function init() {
44+
public function init() {
4345
/* plugin initialization */
4446
$this->hasCpSection = true;
4547
$this->hasCpSettings = true;
@@ -48,7 +50,6 @@ public function init() {
4850
$this->_setComponents();
4951
$this->_setRoutes();
5052
$this->_setEvents();
51-
5253
}
5354

5455
protected function _setComponents() {
@@ -58,6 +59,7 @@ protected function _setComponents() {
5859
'request' => Request::class,
5960
'promptProcessor' => PromptProcessor::class,
6061
'translate' => Translate::class,
62+
'generateEntry' => GenerateEntry::class,
6163
'base' => Base::class,
6264
]);
6365
}
@@ -75,6 +77,7 @@ function (RegisterUrlRulesEvent $event) {
7577
$event->rules['convergine-contentbuddy/settings/fields'] = 'convergine-contentbuddy/settings/fields';
7678
$event->rules['convergine-contentbuddy/settings/general'] = 'convergine-contentbuddy/settings/general';
7779
$event->rules['convergine-contentbuddy/settings/translation'] = 'convergine-contentbuddy/settings/translation';
80+
$event->rules['convergine-contentbuddy/settings/newsletter'] = 'convergine-contentbuddy/settings/newsletter';
7881

7982
$event->rules['convergine-contentbuddy/content-generator'] = 'convergine-contentbuddy/content-generator/index';
8083

@@ -100,6 +103,7 @@ function (Event $event) {
100103
$variable->set('contentbuddy', BuddyVariable::class);
101104
}
102105
);
106+
103107
/**
104108
* Attach button to selected fields.
105109
*/
@@ -110,13 +114,30 @@ static function (DefineFieldHtmlEvent $event) {
110114
/** @var SettingsModel $settings */
111115
$settings = BuddyPlugin::getInstance()->getSettings();
112116

117+
if(get_class($event->sender) == 'craft\fields\Entries') {
118+
$id = $event->element->id;
119+
$type = match(get_class($event->element)) {
120+
'craft\elements\Category' => 'category',
121+
'craft\elements\Asset' => 'asset',
122+
default => 'entry'
123+
};
124+
125+
$replacement = '</button><button type="button" class="btn add cb-btn-generate-entries icon dashed wrap" aria-label="Generate" data-id="'.$id.'" data-type="'.$type.'" data-handle="'.$event->sender->handle.'" data-name="'.$event->sender->name.'">'.self::getIcon('#3F4D5A').' <span class="cb-text">Generate</span></button>';
126+
127+
$pos = strrpos($event->html, '</button>');
128+
if($pos !== false) {
129+
$event->html = substr_replace($event->html, $replacement, $pos, strlen('</button>'));
130+
}
131+
}
132+
113133
if (
114134
array_key_exists($event->sender->handle, $settings->enabledFields)
115135
&& $settings->enabledFields[$event->sender->handle]
116136
&& (($settings->textAi == 'openai' && $settings->apiToken) || ($settings->textAi == 'xai' && $settings->xAiApiKey))
117137
){
118-
$event->html .= Craft::$app->view->renderTemplate('convergine-contentbuddy/_select.twig',
119-
[ 'event' => $event, 'hash' => StringHelper::UUID()] );
138+
$select = Craft::$app->view->renderTemplate('convergine-contentbuddy/_select.twig', [ 'event' => $event, 'hash' => StringHelper::UUID()] );
139+
$ckeditor_padding = Craft::$app->view->renderTemplate('convergine-contentbuddy/_ckeditor_padding.twig');
140+
$event->html .= $select . "<script>".$ckeditor_padding."</script>";
120141
}
121142
}
122143
);
@@ -192,7 +213,6 @@ function (DefineElementEditorHtmlEvent $event) {
192213
function (DefineHtmlEvent $event) {
193214
if (($event->sender->enabled && $event->sender->getEnabledForSite())) {
194215
$event->html.=$this->translate->getEntryTranslateControl($event->sender);
195-
196216
}
197217
}
198218
);
@@ -266,4 +286,13 @@ public function getCpNavItem(): ?array {
266286
public function getSettingsResponse(): mixed {
267287
return Craft::$app->controller->redirect(UrlHelper::cpUrl('convergine-contentbuddy/settings/text-generation'));
268288
}
289+
290+
private static function getIcon($fill = '#666666') : string {
291+
return '<svg xmlns="http://www.w3.org/2000/svg" style="
292+
margin-right: 5px;" width="18" height="18" viewBox="0 0 18 18" fill="none">
293+
<path d="M11.2106 2.76256C11.5573 2.64509 11.9211 2.58446 12.2906 2.58256C13.5032 2.58067 14.6249 3.22487 15.2312 4.27455C15.6215 4.95666 15.7674 5.75434 15.6329 6.52928C15.6083 6.51034 15.5609 6.4857 15.5287 6.46865L14.964 6.14276L12.6089 4.78424H12.6051L11.9344 4.39582C11.7525 4.29161 11.527 4.29161 11.3432 4.39582L7.12934 6.82107V5.04192L10.6081 3.0354C10.8013 2.92361 11.0022 2.83266 11.2106 2.76256ZM4.58092 4.5474C4.58092 4.36361 4.59608 4.1855 4.6245 4.00929C4.88029 2.40256 6.27481 1.17288 7.96112 1.17099L7.95544 1.17667C8.74933 1.17667 9.51291 1.44951 10.1249 1.9573C10.1003 1.96867 10.051 1.99898 10.015 2.01793L9.36891 2.3893L6.42071 4.08508C6.23692 4.1874 6.12702 4.38255 6.12702 4.59666V9.44717L4.58092 8.55854V4.5474ZM1.63841 5.90592C2.03441 5.22192 2.65777 4.69897 3.40051 4.42424V8.68548C3.40051 8.89769 3.5104 9.08717 3.69419 9.19706L7.8986 11.6166L6.34681 12.5128L2.87377 10.5139C2.72788 10.4305 2.59146 10.3377 2.46451 10.2354C1.17041 9.21412 0.789568 7.37433 1.63841 5.90592ZM13.8234 13.3674H13.236V13.4526C13.236 13.6951 13.2095 13.932 13.1622 14.1594C13.2758 14.0665 13.3857 13.9718 13.4918 13.8714C13.5544 13.8145 13.615 13.7577 13.6737 13.697C13.6775 13.6933 13.6813 13.6876 13.687 13.6838C13.776 13.5947 13.8613 13.5038 13.9428 13.409C13.9542 13.3977 13.9636 13.3863 13.9731 13.3749C13.9238 13.3693 13.8746 13.3674 13.8234 13.3674ZM16.6371 7.37054V7.36486C17.0464 6.13897 16.9061 4.7975 16.2525 3.6815C15.2672 1.97625 13.2872 1.09709 11.3546 1.50635C10.4906 0.541936 9.25522 -0.00564037 7.96112 4.38179e-05C5.98681 4.38179e-05 4.23229 1.26762 3.6203 3.13961C2.35083 3.40108 1.25567 4.19308 0.613359 5.31666C-0.377585 7.02191 -0.152112 9.16864 1.17609 10.6314C0.764937 11.8554 0.905147 13.1968 1.56072 14.3071C2.54409 16.0181 4.52598 16.8972 6.46239 16.4899C6.48513 16.5145 6.50786 16.541 6.53439 16.5657C6.86218 16.9181 7.24112 17.2137 7.65607 17.4391C7.8247 17.5339 7.99902 17.6153 8.17902 17.6854C8.70575 17.8939 9.27227 18.0019 9.85017 18C10.4318 18 10.9946 17.8901 11.5156 17.6854C12.7605 17.2004 13.759 16.181 14.191 14.8604C14.676 14.76 15.1384 14.5819 15.5571 14.3356C15.6518 14.2825 15.7447 14.2238 15.8337 14.1613C16.3889 13.7842 16.855 13.2802 17.1922 12.6853C18.1889 10.98 17.9634 8.83327 16.6371 7.37054ZM16.1691 12.1074L16.1748 12.1131C15.9114 12.564 15.5457 12.9467 15.1156 13.229C14.9489 13.3408 14.7689 13.4356 14.5813 13.5152C14.5245 13.5398 14.4676 13.5625 14.4089 13.5834V13.4545C14.2706 13.4109 14.1228 13.3844 13.9731 13.3749C13.9238 13.3693 13.8746 13.3674 13.8234 13.3674H13.2285V13.464C13.2285 13.7065 13.2019 13.9434 13.1527 14.1707C12.823 15.6941 11.4607 16.8347 9.8388 16.8309V16.8252C9.05059 16.8252 8.28133 16.5448 7.67691 16.037C7.70154 16.0257 7.75839 15.9954 7.7887 15.9783L8.42154 15.6164L11.3887 13.9225C11.5725 13.8183 11.6881 13.625 11.6824 13.4128V13.3674H9.96764L7.8247 14.5914L7.18997 14.9532C7.00807 15.0556 6.8186 15.1408 6.62723 15.2071C6.59313 15.2185 6.55713 15.2318 6.52302 15.2413C5.03755 15.7074 3.37966 15.1067 2.57251 13.7065H2.57819C2.18409 13.0301 2.04388 12.2248 2.1822 11.4518C2.20493 11.4688 2.25419 11.4935 2.28451 11.5124L3.04809 11.9539L5.87313 13.5966C6.05692 13.6989 6.28239 13.6989 6.46618 13.5966L9.10743 12.0827C8.55796 11.5958 8.28701 11.0463 8.15628 10.603L7.13312 10.008L7.13881 7.96738L8.51628 7.17728L8.67354 7.08633H8.67733L8.91796 6.94612L9.92406 6.37202L10.4622 6.06318L11.15 5.67097L11.474 5.48529L11.9306 5.75055L13.5354 6.67897L14.947 7.49749C15.1592 7.62065 15.3563 7.76465 15.5325 7.9257C16.6826 8.97159 16.9857 10.7091 16.1691 12.1074Z" fill="'.$fill.'"></path>
294+
<path d="M14.0205 9.85837C14.0188 9.9215 14.0172 9.98298 14.0122 10.0461C13.9856 10.39 13.8876 10.7124 13.7348 11.0015C13.6252 11.2075 13.489 11.3952 13.3295 11.5614C13.2648 11.6278 13.1967 11.691 13.1252 11.7508C12.7997 12.02 12.4044 12.206 11.9709 12.2791C11.9543 12.2841 11.9377 12.2858 11.9211 12.2891C11.8015 12.3057 11.6753 12.3157 11.549 12.3157C11.4095 12.3157 11.2733 12.3024 11.1388 12.2808C11.1205 12.2775 11.1023 12.2742 11.0856 12.2692C10.9395 12.2426 10.7983 12.2011 10.6638 12.1462C10.594 12.1213 10.5276 12.0914 10.4611 12.0582C10.2253 11.9402 10.0127 11.7874 9.82833 11.6046C9.81006 11.5863 9.79179 11.568 9.77352 11.5481C9.34999 11.1061 9.09089 10.5063 9.09421 9.84341C9.09421 9.37155 9.2304 8.93126 9.46127 8.55743C9.52439 8.45442 9.5958 8.35639 9.67387 8.26501C9.92467 7.96428 10.2469 7.72669 10.6139 7.57715C10.8897 7.46085 11.1903 7.39605 11.5059 7.3894C11.5258 7.3894 11.5441 7.3894 11.564 7.3894C11.7517 7.3894 11.936 7.411 12.1121 7.45254C12.5987 7.56386 13.0306 7.81973 13.3578 8.17362C13.7497 8.59398 13.9989 9.15224 14.0188 9.76865C14.0205 9.79855 14.0205 9.83012 14.0205 9.86003V9.85837Z" fill="'.$fill.'"></path>
295+
<path d="M15.5362 13.9535C15.1145 14.2255 14.6488 14.4221 14.1603 14.533C13.7252 15.9914 12.7195 17.1171 11.4657 17.6527C10.9409 17.8787 10.3741 18 9.78825 18C9.2062 18 8.6356 17.8829 8.10508 17.6527C7.92378 17.5753 7.74821 17.4853 7.57837 17.3807V15.1335C7.57837 14.8155 7.63944 14.51 7.74821 14.2359C8.06309 13.4408 8.78636 12.8843 9.62986 12.8843H13.7901C13.8416 12.8843 13.8912 12.8843 13.9408 12.8926C14.0916 12.9031 14.2405 12.9324 14.3798 12.9805C14.4027 12.9847 14.4237 12.9931 14.4466 13.0014C14.4485 13.0014 14.4504 13.0014 14.4523 13.0014C14.4866 13.014 14.521 13.0286 14.5534 13.0454C14.7404 13.127 14.9122 13.2379 15.0668 13.3739C15.2519 13.5329 15.4122 13.7296 15.5362 13.9514V13.9535Z" fill="'.$fill.'"></path>
296+
</svg>';
297+
}
269298
}

src/api/TextApi.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ public function __construct() {
1414
$this->settings = $settings;
1515
}
1616

17-
function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = false, $lang='') {}
17+
function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = false, $instructions = '', $lang = '') {}
1818

1919
protected function getMaxTokensForModel($model): int {
2020
return match($model) {
21-
"grok-beta" => 130000,
22-
"gpt-4o", "gpt-4o-mini", "o1", "o1-mini", "o3-mini" => 15900,
23-
"gpt-4" => 7900,
24-
"gpt-4-turbo", "gpt-3.5-turbo" => 3900,
21+
"grok-beta", "grok-2", "grok-3", "grok-3-fast", "grok-3-mini", "grok-3-mini-fast" => 130000,
22+
"gpt-4o", "gpt-4o-mini", "o1", "o1-mini", "o3-mini", "gpt-4.5-preview" => 15900,
23+
"gpt-4", "grok-2-vision", "grok-vision-beta" => 7900,
24+
"gpt-4-turbo", "gpt-3.5-turbo" => 3800,
2525
default => 2000
2626
};
2727
}

src/api/text/DeepL.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use yii\helpers\StringHelper;
1111

1212
class DeepL extends TextApi {
13-
public function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = false, $lang=''): string {
13+
public function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = false, $instructions = '', $lang = ''): string {
1414
try {
1515
$translateData = $this->_getTranslatedData($prompt,$lang);
1616
$client = new Client();

src/api/text/OpenAi.php

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,25 @@
33
namespace convergine\contentbuddy\api\text;
44

55
use convergine\contentbuddy\api\TextApi;
6+
use Craft;
67
use Exception;
78
use GuzzleHttp\Client;
89
use Throwable;
910
use yii\helpers\StringHelper;
1011

1112
class OpenAi extends TextApi {
12-
public function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = false, $lang=''): string {
13+
public function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = false, $instructions = '', $lang = ''): string {
1314
try {
1415

1516
$model = $isTranslate?$this->settings->preferredTranslationModel:$this->settings->preferredModel;
1617

1718
$maxTokens = min( $maxTokens, $this->getMaxTokensForModel( $model ) );
18-
if($isTranslate && $model!='gpt-4o-mini' && $model!='gpt-4o'){
19-
$maxTokens = $maxTokens /2;
20-
}
19+
20+
Craft::info( "Translate with OpenAI, max tokens: ".$maxTokens , 'content-buddy' );
2121

2222
$client = new Client();
2323
$res = $client->request( 'POST', $this->getEndpoint( $model ), [
24-
'body' => $this->buildTextGenerationRequestBody( $model, $prompt, $maxTokens, $temperature ),
24+
'body' => $this->buildTextGenerationRequestBody($model, $prompt, $maxTokens, $temperature, $isTranslate, $instructions),
2525
'headers' => [
2626
'Authorization' => 'Bearer ' . $this->settings->getOpenAiApiKey(),
2727
'Content-Type' => 'application/json',
@@ -50,7 +50,7 @@ public function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = fa
5050
return $this->getTextGenerationBasedOnModel( $model, $choices );
5151
}
5252

53-
private function buildTextGenerationRequestBody($model, $prompt, $maxTokensToGenerate, $temperature = 0.7) {
53+
private function buildTextGenerationRequestBody($model, $prompt, $maxTokensToGenerate, $temperature = 0.7, $isTranslate = false, $instructions = '') {
5454
$messages = [];
5555

5656
$systemMessage = $this->settings->systemMessage;
@@ -61,6 +61,28 @@ private function buildTextGenerationRequestBody($model, $prompt, $maxTokensToGen
6161
];
6262
}
6363

64+
if($isTranslate) {
65+
$systemContent = '';
66+
if(str_contains($prompt, '</craft-entry>')) {
67+
$systemContent = 'You are a translator. Do NOT remove, add new, translate, or alter any HTML (this includes <iframe> tags) or custom tags, especially <craft-entry> tags. These tags must remain exactly as they appear in the input. Example: \'<craft-entry data-entry-id="24"></craft-entry>\' should never be modified. Keep the tags in the same order and format as the original text.';
68+
} else if(preg_match('/<[^>]*>/', $prompt)) {
69+
$systemContent = 'You are a translator. Do NOT remove, add new, translate, or alter any HTML (this includes <iframe> tags) or custom tags. Keep the tags in the same order and format as the original text.';
70+
}
71+
if(!empty($systemContent)) {
72+
$messages[] = [
73+
'role' => 'system',
74+
'content' => $systemContent
75+
];
76+
}
77+
}
78+
79+
if(!empty($instructions)) {
80+
$messages[] = [
81+
'role' => 'system',
82+
'content' => $instructions,
83+
];
84+
}
85+
6486
$messages[] = [
6587
'role' => 'user',
6688
'content' => $prompt,

src/api/text/XAi.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
use yii\helpers\StringHelper;
1010

1111
class XAi extends TextApi {
12-
public function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = false, $lang=''): string {
12+
public function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = false, $instructions = '', $lang = ''): string {
1313
try {
14-
//TODO
1514
$model = $this->settings->xAiModel;
1615

1716
if($isTranslate) {
@@ -22,7 +21,7 @@ public function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = fa
2221

2322
$client = new Client();
2423
$res = $client->request( 'POST', $this->getEndpoint($model), [
25-
'body' => $this->buildTextGenerationRequestBody( $model, $prompt, $maxTokens, $temperature ),
24+
'body' => $this->buildTextGenerationRequestBody( $model, $prompt, $maxTokens, $temperature, $isTranslate, $instructions ),
2625
'headers' => [
2726
'Authorization' => 'Bearer ' . $this->settings->getXAiApiKey(),
2827
'Content-Type' => 'application/json',
@@ -50,7 +49,7 @@ public function sendRequest($prompt, $maxTokens, $temperature, $isTranslate = fa
5049
return $this->getTextGenerationBasedOnModel( $model, $choices );
5150
}
5251

53-
private function buildTextGenerationRequestBody($model, $prompt, $maxTokensToGenerate, $temperature = 0.7) : string {
52+
private function buildTextGenerationRequestBody($model, $prompt, $maxTokensToGenerate, $temperature = 0.7, $isTranslate = false, $instructions = '') : string {
5453
$messages = [];
5554

5655
$systemMessage = $this->settings->systemMessage;
@@ -61,6 +60,28 @@ private function buildTextGenerationRequestBody($model, $prompt, $maxTokensToGen
6160
];
6261
}
6362

63+
if($isTranslate) {
64+
$systemContent = '';
65+
if(str_contains($prompt, '</craft-entry>')) {
66+
$systemContent = 'You are a translator. Do NOT remove, add new, translate, or alter any HTML (this includes <iframe> tags) or custom tags, especially <craft-entry> tags. These tags must remain exactly as they appear in the input. Example: \'<craft-entry data-entry-id="24"></craft-entry>\' should never be modified. Keep the tags in the same order and format as the original text.';
67+
} else if(preg_match('/<[^>]*>/', $prompt)) {
68+
$systemContent = 'You are a translator. Do NOT remove, add new, translate, or alter any HTML (this includes <iframe> tags) or custom tags. Keep the tags in the same order and format as the original text.';
69+
}
70+
if(!empty($systemContent)) {
71+
$messages[] = [
72+
'role' => 'system',
73+
'content' => $systemContent
74+
];
75+
}
76+
}
77+
78+
if(!empty($instructions)) {
79+
$messages[] = [
80+
'role' => 'system',
81+
'content' => $instructions,
82+
];
83+
}
84+
6485
$messages[] = [
6586
'role' => 'user',
6687
'content' => $prompt,

0 commit comments

Comments
 (0)