mirror of
https://github.com/WenPai-org/wpmind.git
synced 2025-08-03 02:48:41 +08:00
added support for Anthropic and it is recommended
move code to work with AI from rest class to separate class
This commit is contained in:
parent
1eda5f0e15
commit
e67eca2ee0
14 changed files with 797 additions and 342 deletions
521
classes/class-ai-api.php
Normal file
521
classes/class-ai-api.php
Normal file
|
@ -0,0 +1,521 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin AI API functions.
|
||||
*
|
||||
* @package mind
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mind AI API class.
|
||||
*/
|
||||
class Mind_AI_API {
|
||||
/**
|
||||
* Buffer for streaming response.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $buffer = '';
|
||||
|
||||
/**
|
||||
* Last time the buffer was sent.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $last_send_time = 0;
|
||||
|
||||
/**
|
||||
* Buffer threshold.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private const BUFFER_THRESHOLD = 150;
|
||||
|
||||
/**
|
||||
* Minimum send interval.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
private const MIN_SEND_INTERVAL = 0.05;
|
||||
|
||||
/**
|
||||
* The single class instance.
|
||||
*
|
||||
* @var null
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Main Instance
|
||||
* Ensures only one instance of this class exists in memory at any one time.
|
||||
*/
|
||||
public static function instance() {
|
||||
if ( is_null( self::$instance ) ) {
|
||||
self::$instance = new self();
|
||||
self::$instance->init();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the class.
|
||||
*/
|
||||
public function init() {
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connected model.
|
||||
* The same function placed in /utils/is-ai-connected/
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public function get_connected_model() {
|
||||
$settings = get_option( 'mind_settings', array() );
|
||||
$ai_model = $settings['ai_model'] ?? '';
|
||||
$result = false;
|
||||
|
||||
if ( $ai_model ) {
|
||||
if ( 'gpt-4o' === $ai_model || 'gpt-4o-mini' === $ai_model ) {
|
||||
if ( ! empty( $settings['openai_api_key'] ) ) {
|
||||
$result = [
|
||||
'model' => $ai_model,
|
||||
'key' => $settings['openai_api_key'],
|
||||
];
|
||||
}
|
||||
} elseif ( ! empty( $settings['anthropic_api_key'] ) ) {
|
||||
$result = [
|
||||
'model' => 'claude-3-5-haiku' === $ai_model ? 'claude-3-5-haiku' : 'claude-3-5-sonnet',
|
||||
'key' => $settings['anthropic_api_key'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send request to API.
|
||||
*
|
||||
* @param string $request request text.
|
||||
* @param string $context context.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function request( $request, $context ) {
|
||||
// Set headers for streaming.
|
||||
header( 'Content-Type: text/event-stream' );
|
||||
header( 'Cache-Control: no-cache' );
|
||||
header( 'Connection: keep-alive' );
|
||||
header( 'X-Accel-Buffering: no' );
|
||||
|
||||
ob_implicit_flush( true );
|
||||
ob_end_flush();
|
||||
|
||||
if ( ! $request ) {
|
||||
$this->send_stream_error( 'no_request', __( 'Provide request to receive AI response.', 'mind' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
$connected_model = $this->get_connected_model();
|
||||
|
||||
if ( ! $connected_model ) {
|
||||
$this->send_stream_error( 'no_model_connected', __( 'Select an AI model and provide API key in the plugin settings.', 'mind' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
$messages = $this->prepare_messages( $request, $context );
|
||||
|
||||
if ( 'gpt-4o' === $connected_model['model'] || 'gpt-4o-mini' === $connected_model['model'] ) {
|
||||
$this->request_open_ai( $connected_model['model'], $connected_model['key'], $messages );
|
||||
} else {
|
||||
$this->request_anthropic( $connected_model['model'], $connected_model['key'], $messages );
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare messages for request.
|
||||
*
|
||||
* @param string $user_query user query.
|
||||
* @param string $context context.
|
||||
*/
|
||||
public function prepare_messages( $user_query, $context ) {
|
||||
$messages = [];
|
||||
|
||||
$messages[] = [
|
||||
'role' => 'system',
|
||||
'content' => Mind_Prompts::get_system_prompt( $user_query, $context ),
|
||||
];
|
||||
|
||||
// Optional blocks JSON context.
|
||||
if ( $context ) {
|
||||
$messages[] = [
|
||||
'role' => 'user',
|
||||
'content' => '<context>' . $context . '</context>',
|
||||
];
|
||||
}
|
||||
|
||||
// User Query.
|
||||
$messages[] = [
|
||||
'role' => 'user',
|
||||
'content' => '<user_query>' . $user_query . '</user_query>',
|
||||
];
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert OpenAI messages format to Anthropic format.
|
||||
*
|
||||
* @param array $openai_messages Array of messages in OpenAI format.
|
||||
* @return array Messages in Anthropic format
|
||||
*/
|
||||
public function convert_to_anthropic_messages( $openai_messages ) {
|
||||
$system = [];
|
||||
$messages = [];
|
||||
|
||||
foreach ( $openai_messages as $message ) {
|
||||
if ( 'system' === $message['role'] ) {
|
||||
$allow_cache = strlen( $message['content'] ) > 2100;
|
||||
|
||||
// Convert system message.
|
||||
$system[] = array_merge(
|
||||
array(
|
||||
'type' => 'text',
|
||||
'text' => $message['content'],
|
||||
),
|
||||
$allow_cache ? array(
|
||||
'cache_control' => [ 'type' => 'ephemeral' ],
|
||||
) : array()
|
||||
);
|
||||
} else {
|
||||
// Convert user/assistant messages.
|
||||
$messages[] = [
|
||||
'role' => 'assistant' === $message['role'] ? 'assistant' : 'user',
|
||||
'content' => $message['content'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'system' => $system,
|
||||
'messages' => $messages,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request Anthropic API.
|
||||
*
|
||||
* @param string $model model.
|
||||
* @param string $key key.
|
||||
* @param array $messages messages.
|
||||
*/
|
||||
public function request_anthropic( $model, $key, $messages ) {
|
||||
$anthropic_messages = $this->convert_to_anthropic_messages( $messages );
|
||||
$anthropic_version = '2023-06-01';
|
||||
|
||||
if ( 'claude-3-5-haiku' === $model ) {
|
||||
$model = 'claude-3-5-haiku-20241022';
|
||||
} else {
|
||||
$model = 'claude-3-5-sonnet-20241022';
|
||||
}
|
||||
|
||||
$body = [
|
||||
'model' => $model,
|
||||
'max_tokens' => 8192,
|
||||
'system' => $anthropic_messages['system'],
|
||||
'messages' => $anthropic_messages['messages'],
|
||||
'stream' => true,
|
||||
];
|
||||
|
||||
/* phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init, WordPress.WP.AlternativeFunctions.curl_curl_setopt, WordPress.WP.AlternativeFunctions.curl_curl_exec, WordPress.WP.AlternativeFunctions.curl_curl_errno, WordPress.WP.AlternativeFunctions.curl_curl_error, WordPress.WP.AlternativeFunctions.curl_curl_close */
|
||||
|
||||
$ch = curl_init( 'https://api.anthropic.com/v1/messages' );
|
||||
curl_setopt( $ch, CURLOPT_POST, 1 );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt(
|
||||
$ch,
|
||||
CURLOPT_HTTPHEADER,
|
||||
[
|
||||
'Content-Type: application/json',
|
||||
'x-api-key: ' . $key,
|
||||
'anthropic-version: ' . $anthropic_version,
|
||||
]
|
||||
);
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, wp_json_encode( $body ) );
|
||||
curl_setopt(
|
||||
$ch,
|
||||
CURLOPT_WRITEFUNCTION,
|
||||
function ( $curl, $data ) {
|
||||
// Response with error message.
|
||||
if ( $data && strpos( $data, '{"type":"error","error":{' ) !== false ) {
|
||||
$error_data = json_decode( $data, true );
|
||||
|
||||
if ( isset( $error_data['error']['message'] ) ) {
|
||||
$this->send_stream_error( 'anthropic_error', $error_data['error']['message'] );
|
||||
}
|
||||
|
||||
return strlen( $data );
|
||||
}
|
||||
|
||||
$this->process_anthropic_stream_chunk( $data );
|
||||
|
||||
return strlen( $data );
|
||||
}
|
||||
);
|
||||
|
||||
curl_exec( $ch );
|
||||
|
||||
if ( curl_errno( $ch ) ) {
|
||||
$this->send_stream_error( 'curl_error', curl_error( $ch ) );
|
||||
}
|
||||
|
||||
curl_close( $ch );
|
||||
}
|
||||
|
||||
/**
|
||||
* Request OpenAI API.
|
||||
*
|
||||
* @param string $model model.
|
||||
* @param string $key key.
|
||||
* @param array $messages messages.
|
||||
*/
|
||||
public function request_open_ai( $model, $key, $messages ) {
|
||||
$body = [
|
||||
'model' => $model,
|
||||
'stream' => true,
|
||||
'top_p' => 0.9,
|
||||
'temperature' => 0.7,
|
||||
'messages' => $messages,
|
||||
];
|
||||
|
||||
/* phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init, WordPress.WP.AlternativeFunctions.curl_curl_setopt, WordPress.WP.AlternativeFunctions.curl_curl_exec, WordPress.WP.AlternativeFunctions.curl_curl_errno, WordPress.WP.AlternativeFunctions.curl_curl_error, WordPress.WP.AlternativeFunctions.curl_curl_close */
|
||||
|
||||
$ch = curl_init( 'https://api.openai.com/v1/chat/completions' );
|
||||
curl_setopt( $ch, CURLOPT_POST, 1 );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt(
|
||||
$ch,
|
||||
CURLOPT_HTTPHEADER,
|
||||
[
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $key,
|
||||
]
|
||||
);
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, wp_json_encode( $body ) );
|
||||
curl_setopt(
|
||||
$ch,
|
||||
CURLOPT_WRITEFUNCTION,
|
||||
function ( $curl, $data ) {
|
||||
// Response with error message.
|
||||
if ( $data && strpos( $data, "{\n \"error\": {\n \"message\":" ) !== false ) {
|
||||
$error_data = json_decode( $data, true );
|
||||
|
||||
if ( isset( $error_data['error']['message'] ) ) {
|
||||
$this->send_stream_error( 'openai_error', $error_data['error']['message'] );
|
||||
}
|
||||
|
||||
return strlen( $data );
|
||||
}
|
||||
|
||||
$this->process_openai_stream_chunk( $data );
|
||||
|
||||
return strlen( $data );
|
||||
}
|
||||
);
|
||||
|
||||
curl_exec( $ch );
|
||||
|
||||
if ( curl_errno( $ch ) ) {
|
||||
$this->send_stream_error( 'curl_error', curl_error( $ch ) );
|
||||
}
|
||||
|
||||
curl_close( $ch );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process streaming chunk from OpenAI
|
||||
*
|
||||
* @param string $chunk - chunk of data.
|
||||
*/
|
||||
private function process_openai_stream_chunk( $chunk ) {
|
||||
$lines = explode( "\n", $chunk );
|
||||
|
||||
foreach ( $lines as $line ) {
|
||||
if ( strlen( trim( $line ) ) === 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( strpos( $line, 'data: ' ) === 0 ) {
|
||||
$json_data = trim( substr( $line, 6 ) );
|
||||
|
||||
if ( '[DONE]' === $json_data ) {
|
||||
if ( ! empty( $this->buffer ) ) {
|
||||
$this->send_buffered_chunk();
|
||||
}
|
||||
$this->send_stream_chunk( [ 'done' => true ] );
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = json_decode( $json_data, true );
|
||||
|
||||
if ( isset( $data['choices'][0]['delta']['content'] ) ) {
|
||||
$content = $data['choices'][0]['delta']['content'];
|
||||
|
||||
// Send immediately for JSON markers.
|
||||
if ( strpos( $content, '```json' ) !== false ||
|
||||
strpos( $content, '```' ) !== false ) {
|
||||
if ( ! empty( $this->buffer ) ) {
|
||||
$this->send_buffered_chunk();
|
||||
}
|
||||
$this->send_stream_chunk( [ 'content' => $content ] );
|
||||
$this->last_send_time = microtime( true );
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->buffer .= $content;
|
||||
$current_time = microtime( true );
|
||||
$time_since_last_send = $current_time - $this->last_send_time;
|
||||
|
||||
if ( strlen( $this->buffer ) >= self::BUFFER_THRESHOLD ||
|
||||
$time_since_last_send >= self::MIN_SEND_INTERVAL ||
|
||||
strpos( $this->buffer, "\n" ) !== false ) {
|
||||
$this->send_buffered_chunk();
|
||||
}
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$this->send_stream_error( 'json_error', $e->getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process streaming chunk from Anthropic
|
||||
*
|
||||
* @param string $chunk - chunk of data.
|
||||
*/
|
||||
private function process_anthropic_stream_chunk( $chunk ) {
|
||||
$lines = explode( "\n", $chunk );
|
||||
|
||||
foreach ( $lines as $line ) {
|
||||
if ( strlen( trim( $line ) ) === 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove "data: " prefix if exists.
|
||||
if ( strpos( $line, 'data: ' ) === 0 ) {
|
||||
$json_data = trim( substr( $line, 6 ) );
|
||||
} else {
|
||||
$json_data = trim( $line );
|
||||
}
|
||||
|
||||
// Skip empty events.
|
||||
if ( '' === $json_data ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = json_decode( $json_data, true );
|
||||
|
||||
if ( isset( $data['type'] ) ) {
|
||||
if ( 'content_block_delta' === $data['type'] && isset( $data['delta']['text'] ) ) {
|
||||
$content = $data['delta']['text'];
|
||||
|
||||
// Send immediately for JSON markers.
|
||||
if (
|
||||
strpos( $content, '```json' ) !== false ||
|
||||
strpos( $content, '```' ) !== false
|
||||
) {
|
||||
if ( ! empty( $this->buffer ) ) {
|
||||
$this->send_buffered_chunk();
|
||||
}
|
||||
|
||||
$this->send_stream_chunk( [ 'content' => $content ] );
|
||||
$this->last_send_time = microtime( true );
|
||||
} else {
|
||||
$this->buffer .= $content;
|
||||
$current_time = microtime( true );
|
||||
|
||||
$time_since_last_send = $current_time - $this->last_send_time;
|
||||
|
||||
if (
|
||||
strlen( $this->buffer ) >= self::BUFFER_THRESHOLD ||
|
||||
$time_since_last_send >= self::MIN_SEND_INTERVAL ||
|
||||
strpos( $this->buffer, "\n" ) !== false
|
||||
) {
|
||||
$this->send_buffered_chunk();
|
||||
}
|
||||
}
|
||||
} elseif ( 'message_stop' === $data['type'] ) {
|
||||
if ( ! empty( $this->buffer ) ) {
|
||||
$this->send_buffered_chunk();
|
||||
}
|
||||
|
||||
$this->send_stream_chunk( [ 'done' => true ] );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$this->send_stream_error( 'json_error', $e->getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send buffered chunk
|
||||
*/
|
||||
private function send_buffered_chunk() {
|
||||
if ( empty( $this->buffer ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->send_stream_chunk(
|
||||
[
|
||||
'content' => $this->buffer,
|
||||
]
|
||||
);
|
||||
|
||||
$this->buffer = '';
|
||||
$this->last_send_time = microtime( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send stream chunk
|
||||
*
|
||||
* @param array $data - data to send.
|
||||
*/
|
||||
private function send_stream_chunk( $data ) {
|
||||
echo 'data: ' . wp_json_encode( $data ) . "\n\n";
|
||||
|
||||
if ( ob_get_level() > 0 ) {
|
||||
ob_flush();
|
||||
}
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send stream error
|
||||
*
|
||||
* @param string $code - error code.
|
||||
* @param string $message - error message.
|
||||
*/
|
||||
private function send_stream_error( $code, $message ) {
|
||||
$this->send_stream_chunk(
|
||||
[
|
||||
'error' => true,
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -46,9 +46,8 @@ class Mind_Assets {
|
|||
* Enqueue editor assets
|
||||
*/
|
||||
public function enqueue_block_editor_assets() {
|
||||
$settings = get_option( 'mind_settings', array() );
|
||||
$connected_model = Mind_AI_API::instance()->get_connected_model();
|
||||
|
||||
$openai_key = $settings['openai_api_key'] ?? '';
|
||||
$asset_data = $this->get_asset_file( 'build/editor' );
|
||||
|
||||
wp_enqueue_script(
|
||||
|
@ -63,7 +62,7 @@ class Mind_Assets {
|
|||
'mind-editor',
|
||||
'mindData',
|
||||
[
|
||||
'connected' => ! ! $openai_key,
|
||||
'connected' => ! ! $connected_model,
|
||||
'settingsPageURL' => admin_url( 'admin.php?page=mind&sub_page=settings' ),
|
||||
]
|
||||
);
|
||||
|
|
|
@ -31,13 +31,20 @@ You are Mind - an elite WordPress architect with years of experience in building
|
|||
</response_format>
|
||||
|
||||
<response_format_rules>
|
||||
- Return a valid JSON array of block objects
|
||||
- Each block must include:
|
||||
- name (string): WordPress block identifier
|
||||
- attributes (object): block-specific settings
|
||||
- innerBlocks (array): nested block structures
|
||||
- Ensure proper nesting for columns and groups
|
||||
- Use placeholder URLs from https://placehold.co/ for images
|
||||
- IMPORTANT: Response must start with ```json and end with ```
|
||||
- IMPORTANT: Always return blocks array, even for simple text (use core/paragraph)
|
||||
- Response must be a valid JSON array of block objects
|
||||
- Each block object must include:
|
||||
- name (string): WordPress block identifier (e.g., "core/paragraph", "core/heading")
|
||||
- attributes (object): All required block attributes
|
||||
- innerBlocks (array): Can be empty [] but must be present
|
||||
- For image blocks, use https://placehold.co/:
|
||||
- Format: https://placehold.co/600x400
|
||||
- Sizes: Use common dimensions (600x400, 800x600, 1200x800)
|
||||
- For complex layouts:
|
||||
- Use core/columns with columnCount attribute
|
||||
- Use core/group for section wrapping
|
||||
- Maintain proper block hierarchy
|
||||
</response_format_rules>
|
||||
|
||||
<core_capabilities>
|
||||
|
|
|
@ -13,34 +13,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
* Class Mind_Rest
|
||||
*/
|
||||
class Mind_Rest extends WP_REST_Controller {
|
||||
/**
|
||||
* Buffer for streaming response.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $buffer = '';
|
||||
|
||||
/**
|
||||
* Last time the buffer was sent.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $last_send_time = 0;
|
||||
|
||||
/**
|
||||
* Buffer threshold.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private const BUFFER_THRESHOLD = 150;
|
||||
|
||||
/**
|
||||
* Minimum send interval.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
private const MIN_SEND_INTERVAL = 0.05;
|
||||
|
||||
/**
|
||||
* Namespace.
|
||||
*
|
||||
|
@ -79,7 +51,7 @@ class Mind_Rest extends WP_REST_Controller {
|
|||
]
|
||||
);
|
||||
|
||||
// Request OpenAI API.
|
||||
// Request AI API.
|
||||
register_rest_route(
|
||||
$namespace,
|
||||
'/request_ai/',
|
||||
|
@ -105,7 +77,7 @@ class Mind_Rest extends WP_REST_Controller {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get permissions for OpenAI api request.
|
||||
* Get permissions for AI API request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
@ -136,257 +108,17 @@ class Mind_Rest extends WP_REST_Controller {
|
|||
}
|
||||
|
||||
/**
|
||||
* Prepare messages for request.
|
||||
*
|
||||
* @param string $user_query user query.
|
||||
* @param string $context context.
|
||||
*/
|
||||
public function prepare_messages( $user_query, $context ) {
|
||||
$messages = [];
|
||||
|
||||
$messages[] = [
|
||||
'role' => 'system',
|
||||
'content' => Mind_Prompts::get_system_prompt( $user_query, $context ),
|
||||
];
|
||||
|
||||
// Optional blocks JSON context.
|
||||
if ( $context ) {
|
||||
$messages[] = [
|
||||
'role' => 'user',
|
||||
'content' => '<context>' . $context . '</context>',
|
||||
];
|
||||
}
|
||||
|
||||
// User Query.
|
||||
$messages[] = [
|
||||
'role' => 'user',
|
||||
'content' => '<user_query>' . $user_query . '</user_query>',
|
||||
];
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request OpenAI API.
|
||||
*
|
||||
* @param array $messages messages.
|
||||
*/
|
||||
public function request_open_ai( $messages ) {
|
||||
$settings = get_option( 'mind_settings', array() );
|
||||
$openai_key = $settings['openai_api_key'] ?? '';
|
||||
|
||||
if ( ! $openai_key ) {
|
||||
$this->send_stream_error( 'no_openai_key_found', __( 'Provide OpenAI key in the plugin settings.', 'mind' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
$body = [
|
||||
'model' => 'gpt-4o',
|
||||
'stream' => true,
|
||||
'top_p' => 0.9,
|
||||
'temperature' => 0.7,
|
||||
'messages' => $messages,
|
||||
];
|
||||
|
||||
/* phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init, WordPress.WP.AlternativeFunctions.curl_curl_setopt, WordPress.WP.AlternativeFunctions.curl_curl_exec, WordPress.WP.AlternativeFunctions.curl_curl_errno, WordPress.WP.AlternativeFunctions.curl_curl_error, WordPress.WP.AlternativeFunctions.curl_curl_close */
|
||||
|
||||
$ch = curl_init( 'https://api.openai.com/v1/chat/completions' );
|
||||
curl_setopt( $ch, CURLOPT_POST, 1 );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt(
|
||||
$ch,
|
||||
CURLOPT_HTTPHEADER,
|
||||
[
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $openai_key,
|
||||
]
|
||||
);
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, wp_json_encode( $body ) );
|
||||
curl_setopt(
|
||||
$ch,
|
||||
CURLOPT_WRITEFUNCTION,
|
||||
function ( $curl, $data ) {
|
||||
// Response with error message.
|
||||
if ( $data && strpos( $data, "{\n \"error\": {\n \"message\":" ) !== false ) {
|
||||
$error_data = json_decode( $data, true );
|
||||
|
||||
if ( isset( $error_data['error']['message'] ) ) {
|
||||
$this->send_stream_error( 'openai_error', $error_data['error']['message'] );
|
||||
}
|
||||
|
||||
return strlen( $data );
|
||||
}
|
||||
|
||||
$this->process_stream_chunk( $data );
|
||||
|
||||
return strlen( $data );
|
||||
}
|
||||
);
|
||||
|
||||
curl_exec( $ch );
|
||||
|
||||
if ( curl_errno( $ch ) ) {
|
||||
$this->send_stream_error( 'curl_error', curl_error( $ch ) );
|
||||
}
|
||||
|
||||
curl_close( $ch );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send request to OpenAI.
|
||||
* Send request to AI API.
|
||||
*
|
||||
* @param WP_REST_Request $req request object.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function request_ai( WP_REST_Request $req ) {
|
||||
// Set headers for streaming.
|
||||
header( 'Content-Type: text/event-stream' );
|
||||
header( 'Cache-Control: no-cache' );
|
||||
header( 'Connection: keep-alive' );
|
||||
header( 'X-Accel-Buffering: no' );
|
||||
|
||||
ob_implicit_flush( true );
|
||||
ob_end_flush();
|
||||
|
||||
$request = $req->get_param( 'request' ) ?? '';
|
||||
$context = $req->get_param( 'context' ) ?? '';
|
||||
|
||||
if ( ! $request ) {
|
||||
$this->send_stream_error( 'no_request', __( 'Provide request to receive AI response.', 'mind' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
$messages = $this->prepare_messages( $request, $context );
|
||||
|
||||
$this->request_open_ai( $messages );
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build base string
|
||||
*
|
||||
* @param string $base_uri - url.
|
||||
* @param string $method - method.
|
||||
* @param array $params - params.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function build_base_string( $base_uri, $method, $params ) {
|
||||
$r = [];
|
||||
ksort( $params );
|
||||
foreach ( $params as $key => $value ) {
|
||||
$r[] = "$key=" . rawurlencode( $value );
|
||||
}
|
||||
return $method . '&' . rawurlencode( $base_uri ) . '&' . rawurlencode( implode( '&', $r ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process streaming chunk from OpenAI
|
||||
*
|
||||
* @param string $chunk - chunk of data.
|
||||
*/
|
||||
private function process_stream_chunk( $chunk ) {
|
||||
$lines = explode( "\n", $chunk );
|
||||
|
||||
foreach ( $lines as $line ) {
|
||||
if ( strlen( trim( $line ) ) === 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( strpos( $line, 'data: ' ) === 0 ) {
|
||||
$json_data = trim( substr( $line, 6 ) );
|
||||
|
||||
if ( '[DONE]' === $json_data ) {
|
||||
if ( ! empty( $this->buffer ) ) {
|
||||
$this->send_buffered_chunk();
|
||||
}
|
||||
$this->send_stream_chunk( [ 'done' => true ] );
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = json_decode( $json_data, true );
|
||||
|
||||
if ( isset( $data['choices'][0]['delta']['content'] ) ) {
|
||||
$content = $data['choices'][0]['delta']['content'];
|
||||
|
||||
// Send immediately for JSON markers.
|
||||
if ( strpos( $content, '```json' ) !== false ||
|
||||
strpos( $content, '```' ) !== false ) {
|
||||
if ( ! empty( $this->buffer ) ) {
|
||||
$this->send_buffered_chunk();
|
||||
}
|
||||
$this->send_stream_chunk( [ 'content' => $content ] );
|
||||
$this->last_send_time = microtime( true );
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->buffer .= $content;
|
||||
$current_time = microtime( true );
|
||||
$time_since_last_send = $current_time - $this->last_send_time;
|
||||
|
||||
if ( strlen( $this->buffer ) >= self::BUFFER_THRESHOLD ||
|
||||
$time_since_last_send >= self::MIN_SEND_INTERVAL ||
|
||||
strpos( $this->buffer, "\n" ) !== false ) {
|
||||
$this->send_buffered_chunk();
|
||||
}
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$this->send_stream_error( 'json_error', $e->getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send buffered chunk
|
||||
*/
|
||||
private function send_buffered_chunk() {
|
||||
if ( empty( $this->buffer ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->send_stream_chunk(
|
||||
[
|
||||
'content' => $this->buffer,
|
||||
]
|
||||
);
|
||||
|
||||
$this->buffer = '';
|
||||
$this->last_send_time = microtime( true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send stream chunk
|
||||
*
|
||||
* @param array $data - data to send.
|
||||
*/
|
||||
private function send_stream_chunk( $data ) {
|
||||
echo 'data: ' . wp_json_encode( $data ) . "\n\n";
|
||||
|
||||
if ( ob_get_level() > 0 ) {
|
||||
ob_flush();
|
||||
}
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send stream error
|
||||
*
|
||||
* @param string $code - error code.
|
||||
* @param string $message - error message.
|
||||
*/
|
||||
private function send_stream_error( $code, $message ) {
|
||||
$this->send_stream_chunk(
|
||||
[
|
||||
'error' => true,
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
]
|
||||
);
|
||||
Mind_AI_API::instance()->request( $request, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
5
mind.php
5
mind.php
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: AI Mind
|
||||
* Description: Content Assistant Plugin based on OpenAI. Write, improve, rewrite, rephrase, change the tone of your blog posts, and more.
|
||||
* Requires at least: 6.0
|
||||
* Description: AI Page Builder based on Anthropic and OpenAI. Build, design, improve, rewrite your page sections and blocks.
|
||||
* Requires at least: 6.3
|
||||
* Requires PHP: 7.2
|
||||
* Version: 0.2.0
|
||||
* Author: Mind Team
|
||||
|
@ -85,6 +85,7 @@ class Mind {
|
|||
*/
|
||||
private function include_dependencies() {
|
||||
require_once $this->plugin_path . 'classes/class-prompts.php';
|
||||
require_once $this->plugin_path . 'classes/class-ai-api.php';
|
||||
require_once $this->plugin_path . 'classes/class-admin.php';
|
||||
require_once $this->plugin_path . 'classes/class-assets.php';
|
||||
require_once $this->plugin_path . 'classes/class-rest.php';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "mind",
|
||||
"version": "0.2.0",
|
||||
"description": "Mind - Content Assistant Plugin based on OpenAI",
|
||||
"description": "Mind - AI Page Builder based on Anthropic and OpenAI. Build, design, improve, rewrite your page sections and blocks.",
|
||||
"author": "Mind Team",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"files": [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
=== Mind - AI Content Assistant ===
|
||||
=== Mind - AI Page Builder ===
|
||||
Contributors: nko
|
||||
Tags: ai, openai, gpt, copywriting, assistant
|
||||
Tags: ai, gpt, ai page builder, ai editor, copilot
|
||||
Requires at least: 6.2
|
||||
Tested up to: 6.7
|
||||
Requires PHP: 7.2
|
||||
|
@ -8,11 +8,11 @@ Stable tag: 0.2.0
|
|||
License: GPL-2.0-or-later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
AI content assistant and enhancer for WordPress page builder.
|
||||
AI page builder for WordPress which let's you build sections, redesign existing blocks, etc...
|
||||
|
||||
== Description ==
|
||||
|
||||
Mind is a WordPress plugin designed to assist content editors in writing and improving posts. Powered by the OpenAI API, Mind offers a range of features to enhance the content creation process.
|
||||
Mind is a WordPress plugin that transforms your page building experience. Powered by AI technology, it helps you create and modify entire page sections, layouts, and content directly in the WordPress editor. With support for both Anthropic and OpenAI AI models, Mind seamlessly integrates with the WordPress block editor to enhance your page building workflow.
|
||||
|
||||
=== 🚀 Community-Driven Development ===
|
||||
|
||||
|
|
|
@ -21,12 +21,38 @@ import { __ } from '@wordpress/i18n';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import isValidOpenAIApiKey from '../../utils/is-valid-openai-api-key';
|
||||
import isValidAnthropicApiKey from '../../utils/is-valid-anthropic-api-key';
|
||||
import { ReactComponent as LoadingIcon } from '../../icons/loading.svg';
|
||||
|
||||
const models = [
|
||||
{
|
||||
title: __('Claude 3.5 Sonnet', 'mind'),
|
||||
name: 'claude-3-5-sonnet',
|
||||
description: __('Best quality and recommended', 'mind'),
|
||||
},
|
||||
{
|
||||
title: __('Claude 3.5 Haiku', 'mind'),
|
||||
name: 'claude-3-5-haiku',
|
||||
description: __('Fast and accurate', 'mind'),
|
||||
},
|
||||
{
|
||||
title: __('GPT-4o', 'mind'),
|
||||
name: 'gpt-4o',
|
||||
description: __('Quick and reliable', 'mind'),
|
||||
},
|
||||
{
|
||||
title: __('GPT-4o mini', 'mind'),
|
||||
name: 'gpt-4o-mini',
|
||||
description: __('Basic and fastest', 'mind'),
|
||||
},
|
||||
];
|
||||
|
||||
export default function PageSettings() {
|
||||
const [pendingSettings, setPendingSettings] = useState({});
|
||||
const [settingsChanged, setSettingsChanged] = useState(false);
|
||||
const [isInvalidAPIKey, setIsInvalidAPIKey] = useState(false);
|
||||
const [isInvalidAnthropicAPIKey, setIsInvalidAnthropicAPIKey] =
|
||||
useState(false);
|
||||
const [isInvalidOpenAIAPIKey, setIsInvalidOpenAIAPIKey] = useState(false);
|
||||
|
||||
const { updateSettings } = useDispatch('mind/settings');
|
||||
|
||||
|
@ -54,12 +80,118 @@ export default function PageSettings() {
|
|||
<>
|
||||
<div className="mind-admin-settings-card">
|
||||
<div className="mind-admin-settings-card-name">
|
||||
<label htmlFor="mind-settings-openai-api-key">
|
||||
{__('OpenAI API Key', 'mind')}
|
||||
<label htmlFor="mind-settings-ai-model">
|
||||
{__('Model', 'mind')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="mind-admin-settings-card-button-group">
|
||||
{models.map((model) => (
|
||||
<button
|
||||
key={model.title}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setPendingSettings({
|
||||
...pendingSettings,
|
||||
ai_model: model.name,
|
||||
});
|
||||
}}
|
||||
className={clsx(
|
||||
'mind-admin-settings-card-button',
|
||||
pendingSettings.ai_model === model.name &&
|
||||
'mind-admin-settings-card-button-active'
|
||||
)}
|
||||
>
|
||||
{model.title}
|
||||
<span>{model.description}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{pendingSettings.ai_model?.includes('claude') && (
|
||||
<div className="mind-admin-settings-card">
|
||||
<div className="mind-admin-settings-card-name">
|
||||
<label htmlFor="mind-settings-anthropic-api-key">
|
||||
{__('Anthropic API Key', 'mind')}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'mind-admin-settings-card-input',
|
||||
isInvalidAnthropicAPIKey &&
|
||||
'mind-admin-settings-card-input-error'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
id="mind-settings-anthropic-api-key"
|
||||
type="text"
|
||||
placeholder={__('Enter API key', 'mind')}
|
||||
value={pendingSettings.anthropic_api_key || ''}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setPendingSettings({
|
||||
...pendingSettings,
|
||||
anthropic_api_key: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{isInvalidAnthropicAPIKey && (
|
||||
<div className="mind-admin-setting-error">
|
||||
{__('Please enter a valid API key', 'mind')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mind-admin-settings-card-description">
|
||||
{__(
|
||||
'This setting is required, since our plugin works with OpenAI.',
|
||||
'This setting is required to use Anthropic models.',
|
||||
'mind'
|
||||
)}{' '}
|
||||
<a
|
||||
href="https://console.anthropic.com/settings/keys"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{__('Create API key', 'mind')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{pendingSettings.ai_model?.includes('gpt') && (
|
||||
<div className="mind-admin-settings-card">
|
||||
<div className="mind-admin-settings-card-name">
|
||||
<label htmlFor="mind-settings-openai-api-key">
|
||||
{__('OpenAI API Key', 'mind')}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'mind-admin-settings-card-input',
|
||||
isInvalidOpenAIAPIKey &&
|
||||
'mind-admin-settings-card-input-error'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
id="mind-settings-openai-api-key"
|
||||
type="text"
|
||||
placeholder={__('Enter API key', 'mind')}
|
||||
value={pendingSettings.openai_api_key || ''}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setPendingSettings({
|
||||
...pendingSettings,
|
||||
openai_api_key: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{isInvalidOpenAIAPIKey && (
|
||||
<div className="mind-admin-setting-error">
|
||||
{__('Please enter a valid API key', 'mind')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mind-admin-settings-card-description">
|
||||
{__(
|
||||
'This setting is required to use OpenAI models.',
|
||||
'mind'
|
||||
)}{' '}
|
||||
<a
|
||||
|
@ -71,33 +203,8 @@ export default function PageSettings() {
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'mind-admin-settings-card-input',
|
||||
isInvalidAPIKey &&
|
||||
'mind-admin-settings-card-input-error'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
id="mind-settings-openai-api-key"
|
||||
type="text"
|
||||
placeholder={__('Enter API key', 'mind')}
|
||||
value={pendingSettings.openai_api_key || ''}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setPendingSettings({
|
||||
...pendingSettings,
|
||||
openai_api_key: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{isInvalidAPIKey && (
|
||||
<div className="mind-admin-setting-error">
|
||||
{__('Please enter a valid API key', 'mind')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <div className="mind-admin-settings-error">{error}</div>}
|
||||
<div className="mind-admin-settings-actions">
|
||||
<button
|
||||
|
@ -105,14 +212,27 @@ export default function PageSettings() {
|
|||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Check if Anthropic API key is valid.
|
||||
if (
|
||||
!pendingSettings.openai_api_key ||
|
||||
isValidOpenAIApiKey(pendingSettings.openai_api_key)
|
||||
pendingSettings.anthropic_api_key &&
|
||||
!isValidAnthropicApiKey(
|
||||
pendingSettings.anthropic_api_key
|
||||
)
|
||||
) {
|
||||
setIsInvalidAPIKey(false);
|
||||
updateSettings(pendingSettings);
|
||||
setIsInvalidAnthropicAPIKey(true);
|
||||
|
||||
// Check if OpenAI API key is valid.
|
||||
} else if (
|
||||
pendingSettings.openai_api_key &&
|
||||
!isValidOpenAIApiKey(pendingSettings.openai_api_key)
|
||||
) {
|
||||
setIsInvalidOpenAIAPIKey(true);
|
||||
|
||||
// Update settings.
|
||||
} else {
|
||||
setIsInvalidAPIKey(true);
|
||||
setIsInvalidOpenAIAPIKey(false);
|
||||
setIsInvalidAnthropicAPIKey(false);
|
||||
updateSettings(pendingSettings);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -4,25 +4,74 @@
|
|||
|
||||
.mind-admin-settings-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
+ .mind-admin-settings-card {
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
.mind-admin-settings-card-name {
|
||||
flex: 1;
|
||||
max-width: 250px;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
.mind-admin-settings-card-description {
|
||||
font-size: 12px;
|
||||
margin-top: 16px;
|
||||
margin-top: -10px;
|
||||
color: #646464;
|
||||
}
|
||||
|
||||
.mind-admin-settings-card-button-group {
|
||||
display: flex;
|
||||
gap: 3px;
|
||||
border: 1px solid #000;
|
||||
border-radius: 10px;
|
||||
padding: 3px;
|
||||
|
||||
button {
|
||||
padding: 14px 18px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #000;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
align-items: start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
flex: auto;
|
||||
border-radius: 7px;
|
||||
gap: 3px;
|
||||
transition: 0.2s background-color;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
background-color: #eee;
|
||||
outline: 2px solid transparent;
|
||||
}
|
||||
|
||||
&.mind-admin-settings-card-button-active {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
span {
|
||||
opacity: 0.5;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mind-admin-settings-card-input {
|
||||
flex: 1;
|
||||
|
||||
|
@ -34,7 +83,7 @@
|
|||
border-radius: 7px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px rgba(#000, 30%);
|
||||
box-shadow: 0 0 0 3px rgba(#000, 20%);
|
||||
outline: 2px solid transparent;
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +108,7 @@
|
|||
}
|
||||
|
||||
.mind-admin-settings-actions {
|
||||
margin-top: 50px;
|
||||
margin-top: 35px;
|
||||
|
||||
button {
|
||||
display: inline-flex;
|
||||
|
@ -73,12 +122,12 @@
|
|||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
&:focus-visible {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px rgba(#000, 30%);
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 3px rgba(#000, 30%);
|
||||
outline: 2px solid transparent;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { useSelect, useDispatch } from '@wordpress/data';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import FirstLoadingAnimation from './first-loading-animation';
|
||||
import isAIConnected from '../../utils/is-ai-connected';
|
||||
|
||||
export default function PageWelcome() {
|
||||
const { setActivePage } = useDispatch('mind/admin');
|
||||
|
@ -24,6 +25,7 @@ export default function PageWelcome() {
|
|||
settings: getSettings(),
|
||||
};
|
||||
});
|
||||
const isConnected = isAIConnected(settings);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -42,25 +44,25 @@ export default function PageWelcome() {
|
|||
'mind'
|
||||
)}
|
||||
</p>
|
||||
{settings.openai_api_key ? (
|
||||
{isConnected ? (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: __(
|
||||
'To get started, <em>open the page editor</em> and click on the <em>"Open Mind"</em> button in the toolbar',
|
||||
'To get started, <em>open the page editor</em> and click on the <br /><span class="mind-inline-logo">Open Mind</span> button in the toolbar',
|
||||
'mind'
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div>
|
||||
{__('To get started, enter your', 'mind')}
|
||||
{__('To get started,', 'mind')}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setActivePage('settings');
|
||||
}}
|
||||
>
|
||||
{__('OpenAI API key →', 'mind')}
|
||||
{__('select the model and API key →', 'mind')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -27,12 +27,12 @@ export default function NotConnectedScreen() {
|
|||
<div className="mind-popup-connected-screen">
|
||||
<h2>
|
||||
<KeyIcon />
|
||||
{__('OpenAI Key', 'mind')}
|
||||
{__('AI API Key', 'mind')}
|
||||
</h2>
|
||||
<div>
|
||||
<p>
|
||||
{__(
|
||||
'In order to use Mind, you will need to provide your OpenAI API key. Please insert your API key in the plugin settings to get started.',
|
||||
'In order to use Mind, you will need to provide your Anthropic or OpenAI API key. Please insert your API key in the plugin settings to get started.',
|
||||
'mind'
|
||||
)}
|
||||
</p>
|
||||
|
@ -41,6 +41,8 @@ export default function NotConnectedScreen() {
|
|||
<a
|
||||
className="mind-popup-connected-screen-button"
|
||||
href={settingsPageURL}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{__('Go to Settings', 'mind')}
|
||||
</a>
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
@import "../../../../mixins/text-gradient";
|
||||
|
||||
.mind-popup-not-connected {
|
||||
width: 440px;
|
||||
}
|
||||
|
||||
.mind-popup-connected-screen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
22
src/utils/is-ai-connected/index.js
Normal file
22
src/utils/is-ai-connected/index.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Check if AI is connected
|
||||
* The same function is placed in /classes/class-ai-api.php
|
||||
*
|
||||
* @param {Object} settings Settings object
|
||||
*
|
||||
* @return {boolean} is connected
|
||||
*/
|
||||
export default function isAIConnected(settings) {
|
||||
const model = settings.ai_model || '';
|
||||
let result = false;
|
||||
|
||||
if (model) {
|
||||
if ('gpt-4o' === model || 'gpt-4o-mini' === model) {
|
||||
result = !!settings?.openai_api_key;
|
||||
} else if (settings?.anthropic_api_key) {
|
||||
result = !!settings?.anthropic_api_key;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
4
src/utils/is-valid-anthropic-api-key/index.js
Normal file
4
src/utils/is-valid-anthropic-api-key/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
export default function isValidAnthropicApiKey(apiKey) {
|
||||
const regex = /^sk-ant-[a-zA-Z0-9]/;
|
||||
return regex.test(apiKey);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue