PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.3
4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 All 201 releases
← All changes | includes/Core/WriteWithAI.php +98 -3 4.8.0 → 4.9.3 View file →
@@ -64,11 +64,11 @@
64 64 $platform_labels = ModelRegistry::platforms();
65 65 $model_labels = ModelRegistry::models( $active_platform );
66 66
67 67 // Glossary suggestions are existing-terms-only, and the glossaries taxonomy is only
68 - // registered for Pro (see Core\PostType::register_glossaries_taxonomy). So the "Suggest
69 - // glossaries" control must require, on top of the two settings, that Pro is active AND at
70 - // least one glossary term exists — otherwise the modal advertises an offer that can never
68 + // registered by Pro (see BetterDocsPro\Core\GlossaryTaxonomy::register_glossaries_taxonomy).
69 + // So the "Suggest glossaries" control must require, on top of the two settings, that Pro is
70 + // active AND at least one glossary term exists — otherwise the modal advertises an offer that can never
71 71 // return anything. Mirrors the Docs-AI-suite availability check in Core\DocsAISuite.
72 72 $glossary_count = wp_count_terms( array( 'taxonomy' => 'glossaries', 'hide_empty' => false ) );
73 73 $has_glossary_terms = ! is_wp_error( $glossary_count ) && (int) $glossary_count > 0;
74 74 $glossary_suggestions_enabled = (bool) $this->settings->get( 'enable_glossaries', false )
@@ -432,8 +432,103 @@
432 432 return $result['content'];
433 433 } catch ( \Exception $error ) {
434 434 return 'Error: ' . $error->getMessage();
435 435 }
436 + }
437 +
438 + /**
439 + * Generate documentation from an uploaded image using a vision-capable model.
440 + *
441 + * Mirrors generate_openai_response() but sends the picture alongside the text
442 + * prompt as an OpenAI-format multimodal user message
443 + * (`content: [ {type:text}, {type:image_url} ]`). The OpenAI-compatible
444 + * provider forwards that message array to the wire verbatim, so no provider
445 + * change is needed. Only OpenAI vision models are wired — Claude and Gemini
446 + * use a different image envelope, so they are refused with a clear error
447 + * instead of being sent a payload they would reject.
448 + *
449 + * @param string $prompt Composed instruction prompt.
450 + * @param array $image { data_uri:string, mime:string }.
451 + * @param int|null $max_tokens Optional token cap.
452 + * @param array $extra_system Extra system messages (instruction sets).
453 + * @return string|\WP_Error Generated content, or WP_Error on guard/failure.
454 + */
455 + public function generate_vision_response( $prompt, $image, $max_tokens = null, $extra_system = array() ) {
456 + if ( empty( $image['data_uri'] ) ) {
457 + return new \WP_Error( 'ai_vision_no_image', __( 'No image data to send to the AI.', 'betterdocs' ) );
458 + }
459 +
460 + $factory = new ProviderFactory( $this->settings );
461 + $platform = $factory->active_platform();
462 + $model = $factory->active_model( $platform );
463 +
464 + if ( ! $this->platform_supports_vision( $platform, $model ) ) {
465 + return new \WP_Error(
466 + 'ai_no_vision',
467 + sprintf(
468 + /* translators: 1: AI platform id, 2: model name. */
469 + __( 'The configured AI model (%1$s / %2$s) can\'t read images. Switch to an OpenAI vision model such as GPT-4o or GPT-4o mini in BetterDocs → Settings → AI Content Suite, or upload a PDF/DOCX/TXT instead.', 'betterdocs' ),
470 + $platform,
471 + '' !== (string) $model ? $model : 'default'
472 + )
473 + );
474 + }
475 +
476 + try {
477 + $messages = array_merge(
478 + array( array( 'role' => 'system', 'content' => $this->get_system_prompt() ) ),
479 + $this->normalize_extra_system( $extra_system ),
480 + array(
481 + array(
482 + 'role' => 'user',
483 + 'content' => array(
484 + array( 'type' => 'text', 'text' => (string) $prompt ),
485 + array( 'type' => 'image_url', 'image_url' => array( 'url' => (string) $image['data_uri'] ) ),
486 + ),
487 + ),
488 + )
489 + );
490 +
491 + $result = $factory->make()->chat( $messages, $this->ai_chat_options( $max_tokens ) );
492 +
493 + if ( is_wp_error( $result ) ) {
494 + return $result;
495 + }
496 +
497 + return $result['content'];
498 + } catch ( \Exception $error ) {
499 + return new \WP_Error( 'ai_vision_failed', 'Error: ' . $error->getMessage() );
500 + }
501 + }
502 +
503 + /**
504 + * Whether the active platform + model can accept image input in the OpenAI
505 + * multimodal format. Deliberately conservative: only OpenAI vision model
506 + * families qualify, because Claude and Gemini require a different image
507 + * envelope this path does not build. gpt-3.5 (text-only) is excluded.
508 + *
509 + * @param string $platform
510 + * @param string $model
511 + * @return bool
512 + */
513 + protected function platform_supports_vision( $platform, $model ) {
514 + if ( 'openai' !== $platform ) {
515 + return false;
516 + }
517 +
518 + $model = strtolower( (string) $model );
519 +
520 + if ( '' === $model || false !== strpos( $model, 'gpt-3.5' ) ) {
521 + return false;
522 + }
523 +
524 + foreach ( array( 'gpt-4o', 'gpt-4.1', 'gpt-4-turbo', 'gpt-4-vision', 'chatgpt-4o', 'gpt-5', 'o1', 'o3', 'o4' ) as $family ) {
525 + if ( false !== strpos( $model, $family ) ) {
526 + return true;
527 + }
528 + }
529 +
530 + return false;
436 531 }
437 532
438 533 public function get_outline_system_prompt() {
439 534 $prompt = <<<'PROMPT'