Major refactoring cleanup: - Add new controller architecture (class-controller-*.php) - Add new settings-v2 UI (views/settings-v2/) - Add new CSS architecture (agentic-sidebar.css, tokens) - Add esbuild build pipeline (scripts/build.js, package.json) - Add composer dependencies (vendor/) - Add frontend src directory (assets/js/src/index.jsx) - Add documentation files - Remove old/obsolete files (class-settings.php, old CSS) This commits all pending changes from previous refactoring efforts.
503 lines
16 KiB
PHP
503 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Conversation REST Controller
|
|
*
|
|
* Handles conversation CRUD operations.
|
|
*
|
|
* @package WP_Agentic_Writer
|
|
*/
|
|
|
|
/**
|
|
* Class WP_Agentic_Writer_Controller_Conversation
|
|
*
|
|
* REST controller for conversation operations.
|
|
*
|
|
* @since 0.3.0
|
|
*/
|
|
class WP_Agentic_Writer_Controller_Conversation {
|
|
|
|
/**
|
|
* Sidebar instance for dependency access.
|
|
*
|
|
* @var WP_Agentic_Writer_Gutenberg_Sidebar
|
|
*/
|
|
private $sidebar;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @since 0.3.0
|
|
* @param WP_Agentic_Writer_Gutenberg_Sidebar $sidebar Sidebar instance.
|
|
*/
|
|
public function __construct( $sidebar ) {
|
|
$this->sidebar = $sidebar;
|
|
}
|
|
|
|
/**
|
|
* Handle get conversations request.
|
|
*
|
|
* Lists all conversations for the current user, optionally filtered by post.
|
|
*
|
|
* @since 0.1.4
|
|
* @param WP_REST_Request $request REST request.
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function handle_get_conversations( $request ) {
|
|
$manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
|
|
|
|
$status = sanitize_text_field( $request->get_param( 'status' ) ?: 'active' );
|
|
$limit = (int) $request->get_param( 'limit' ) ?: 20;
|
|
$post_id = (int) $request->get_param( 'post_id' ) ?: 0;
|
|
|
|
// If post_id is specified, check authorization before returning session.
|
|
if ( $post_id > 0 ) {
|
|
// Authorization: User must be able to edit this post.
|
|
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
|
return new WP_Error(
|
|
'forbidden',
|
|
__(
|
|
'You do not have permission to access this post.',
|
|
'wp-agentic-writer',
|
|
),
|
|
[ 'status' => 403 ],
|
|
);
|
|
}
|
|
|
|
$sessions = $manager->get_sessions_for_post( $post_id );
|
|
return new WP_REST_Response(
|
|
[
|
|
'sessions' => $sessions,
|
|
'count' => count( $sessions ),
|
|
],
|
|
200,
|
|
);
|
|
}
|
|
|
|
if ( $request->get_param( 'uncompleted' ) ) {
|
|
$sessions = $manager->get_uncompleted_sessions( $limit );
|
|
} else {
|
|
$sessions = $manager->get_user_sessions( $status, $limit );
|
|
}
|
|
|
|
return new WP_REST_Response(
|
|
[
|
|
'sessions' => $sessions,
|
|
'count' => count( $sessions ),
|
|
],
|
|
200,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle create conversation request.
|
|
*
|
|
* @since 0.1.4
|
|
* @param WP_REST_Request $request REST request.
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function handle_create_conversation( $request ) {
|
|
$params = $request->get_json_params();
|
|
$manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
|
|
|
|
$post_id = isset( $params['post_id'] ) ? (int) $params['post_id'] : 0;
|
|
$focus_keyword = isset( $params['focus_keyword'] )
|
|
? sanitize_text_field( $params['focus_keyword'] )
|
|
: '';
|
|
$title = isset( $params['title'] )
|
|
? sanitize_text_field( $params['title'] )
|
|
: '';
|
|
|
|
// Authorization: If linking to a post, check edit permission.
|
|
if ( $post_id > 0 && ! current_user_can( 'edit_post', $post_id ) ) {
|
|
return new WP_Error(
|
|
'forbidden',
|
|
__(
|
|
'You do not have permission to create a session for this post.',
|
|
'wp-agentic-writer',
|
|
),
|
|
[ 'status' => 403 ],
|
|
);
|
|
}
|
|
|
|
if ( '' === $title && $post_id > 0 ) {
|
|
$post = get_post( $post_id );
|
|
$base_title = $post ? sanitize_text_field( $post->post_title ) : '';
|
|
if ( '' === $base_title ) {
|
|
$base_title = 'Conversation';
|
|
}
|
|
$title = sprintf( '%s - %s', $base_title, current_time( 'Y-m-d H:i' ) );
|
|
}
|
|
|
|
$session_id = $manager->create_session(
|
|
[
|
|
'post_id' => $post_id,
|
|
'focus_keyword' => $focus_keyword,
|
|
'title' => $title,
|
|
]
|
|
);
|
|
|
|
if ( is_wp_error( $session_id ) ) {
|
|
return $session_id;
|
|
}
|
|
|
|
$session = $manager->get_session( $session_id );
|
|
|
|
// MEMANTO: New session created.
|
|
do_action(
|
|
'wpaw_memanto_session_start',
|
|
$session_id,
|
|
$post_id,
|
|
get_current_user_id(),
|
|
);
|
|
|
|
return new WP_REST_Response( $session, 201 );
|
|
}
|
|
|
|
/**
|
|
* Handle get single conversation request.
|
|
*
|
|
* @since 0.1.4
|
|
* @param WP_REST_Request $request REST request.
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function handle_get_conversation( $request ) {
|
|
$session_id = sanitize_text_field( $request->get_param( 'session_id' ) );
|
|
$manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
|
|
|
|
// Check authorization.
|
|
if ( ! $manager->current_user_can_access( $session_id ) ) {
|
|
return new WP_Error(
|
|
'forbidden',
|
|
__(
|
|
'You do not have permission to access this conversation.',
|
|
'wp-agentic-writer',
|
|
),
|
|
[ 'status' => 403 ],
|
|
);
|
|
}
|
|
|
|
$session = $manager->get_session( $session_id );
|
|
|
|
if ( ! $session ) {
|
|
return new WP_Error(
|
|
'not_found',
|
|
__( 'Conversation not found.', 'wp-agentic-writer' ),
|
|
[ 'status' => 404 ],
|
|
);
|
|
}
|
|
|
|
$session = $this->hydrate_session_plan_messages( $session );
|
|
|
|
return new WP_REST_Response( $session, 200 );
|
|
}
|
|
|
|
/**
|
|
* Restore rich plan UI payloads for sessions that only stored a text summary.
|
|
*
|
|
* @since 0.2.2
|
|
* @param array $session Conversation session.
|
|
* @return array
|
|
*/
|
|
private function hydrate_session_plan_messages( $session ) {
|
|
if ( ! is_array( $session ) ) {
|
|
return $session;
|
|
}
|
|
|
|
$post_id = isset( $session['post_id'] ) ? (int) $session['post_id'] : 0;
|
|
if (
|
|
$post_id <= 0
|
|
|| empty( $session['messages'] )
|
|
|| ! is_array( $session['messages'] )
|
|
) {
|
|
return $session;
|
|
}
|
|
|
|
foreach ( $session['messages'] as $message ) {
|
|
if (
|
|
isset( $message['type'] )
|
|
&& 'plan' === $message['type']
|
|
&& ! empty( $message['plan'] )
|
|
) {
|
|
return $session;
|
|
}
|
|
}
|
|
|
|
$plan = get_post_meta( $post_id, '_wpaw_plan', true );
|
|
if ( ! is_array( $plan ) ) {
|
|
return $session;
|
|
}
|
|
|
|
foreach ( $session['messages'] as $index => $message ) {
|
|
$content = isset( $message['content'] )
|
|
? (string) $message['content']
|
|
: '';
|
|
$role = isset( $message['role'] ) ? (string) $message['role'] : '';
|
|
if (
|
|
'assistant' !== $role
|
|
|| false === strpos( $content, 'Outline ready.' )
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
$session['messages'][ $index ]['type'] = 'plan';
|
|
$session['messages'][ $index ]['plan'] = $plan;
|
|
break;
|
|
}
|
|
|
|
return $session;
|
|
}
|
|
|
|
/**
|
|
* Handle update conversation request.
|
|
*
|
|
* @since 0.1.4
|
|
* @param WP_REST_Request $request REST request.
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function handle_update_conversation( $request ) {
|
|
$params = $request->get_json_params();
|
|
$session_id = sanitize_text_field( $request->get_param( 'session_id' ) );
|
|
$manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
|
|
|
|
// Check authorization.
|
|
if ( ! $manager->current_user_can_access( $session_id ) ) {
|
|
return new WP_Error(
|
|
'forbidden',
|
|
__(
|
|
'You do not have permission to modify this conversation.',
|
|
'wp-agentic-writer',
|
|
),
|
|
[ 'status' => 403 ],
|
|
);
|
|
}
|
|
|
|
$session = $manager->get_session( $session_id );
|
|
|
|
if ( ! $session ) {
|
|
return new WP_Error(
|
|
'not_found',
|
|
__( 'Conversation not found.', 'wp-agentic-writer' ),
|
|
[ 'status' => 404 ],
|
|
);
|
|
}
|
|
|
|
// Update fields.
|
|
if ( isset( $params['title'] ) ) {
|
|
$manager->update_title( $session_id, $params['title'] );
|
|
}
|
|
|
|
if ( isset( $params['focus_keyword'] ) ) {
|
|
$manager->update_focus_keyword(
|
|
$session_id,
|
|
$params['focus_keyword'],
|
|
);
|
|
}
|
|
|
|
if ( isset( $params['status'] ) ) {
|
|
if ( 'completed' === $params['status'] ) {
|
|
$manager->mark_completed( $session_id );
|
|
|
|
// MEMANTO: Session completed.
|
|
$post_id = $session['post_id'] ?? 0;
|
|
do_action(
|
|
'wpaw_memanto_session_end',
|
|
$session_id,
|
|
(int) $post_id,
|
|
);
|
|
}
|
|
}
|
|
|
|
$updated_session = $manager->get_session( $session_id );
|
|
|
|
return new WP_REST_Response( $updated_session, 200 );
|
|
}
|
|
|
|
/**
|
|
* Handle delete conversation request.
|
|
*
|
|
* @since 0.1.4
|
|
* @param WP_REST_Request $request REST request.
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function handle_delete_conversation( $request ) {
|
|
$session_id = sanitize_text_field( $request->get_param( 'session_id' ) );
|
|
$manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
|
|
|
|
// Check authorization.
|
|
if ( ! $manager->current_user_can_access( $session_id ) ) {
|
|
return new WP_Error(
|
|
'forbidden',
|
|
__(
|
|
'You do not have permission to delete this conversation.',
|
|
'wp-agentic-writer',
|
|
),
|
|
[ 'status' => 403 ],
|
|
);
|
|
}
|
|
|
|
$result = $manager->delete_session( $session_id );
|
|
|
|
if ( ! $result ) {
|
|
return new WP_Error(
|
|
'delete_failed',
|
|
__( 'Failed to delete conversation.', 'wp-agentic-writer' ),
|
|
[ 'status' => 500 ],
|
|
);
|
|
}
|
|
|
|
// MEMANTO: Session deleted — treat as session end.
|
|
$session = $manager->get_session_unchecked( $session_id );
|
|
$post_id = $session ? $session['post_id'] ?? 0 : 0;
|
|
do_action( 'wpaw_memanto_session_end', $session_id, (int) $post_id );
|
|
|
|
return new WP_REST_Response( [ 'deleted' => true ], 200 );
|
|
}
|
|
|
|
/**
|
|
* Handle update conversation messages request.
|
|
*
|
|
* @since 0.1.4
|
|
* @param WP_REST_Request $request REST request.
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function handle_update_conversation_messages( $request ) {
|
|
$params = $request->get_json_params();
|
|
$session_id = sanitize_text_field( $request->get_param( 'session_id' ) );
|
|
$manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
|
|
|
|
// Check authorization.
|
|
if ( ! $manager->current_user_can_access( $session_id ) ) {
|
|
return new WP_Error(
|
|
'forbidden',
|
|
__(
|
|
'You do not have permission to modify this conversation.',
|
|
'wp-agentic-writer',
|
|
),
|
|
[ 'status' => 403 ],
|
|
);
|
|
}
|
|
|
|
$session = $manager->get_session( $session_id );
|
|
|
|
if ( ! $session ) {
|
|
return new WP_Error(
|
|
'not_found',
|
|
__( 'Conversation not found.', 'wp-agentic-writer' ),
|
|
[ 'status' => 404 ],
|
|
);
|
|
}
|
|
|
|
$messages = isset( $params['messages'] ) ? $params['messages'] : [];
|
|
|
|
if ( ! is_array( $messages ) ) {
|
|
return new WP_Error(
|
|
'invalid_messages',
|
|
__( 'Messages must be an array.', 'wp-agentic-writer' ),
|
|
[ 'status' => 400 ],
|
|
);
|
|
}
|
|
|
|
// Safety: refuse to overwrite existing messages with an empty array.
|
|
// This prevents race conditions during session switches from wiping
|
|
// conversation history. Intentional clears go through clear-context.
|
|
$existing_count = count( $session['messages'] ?? [] );
|
|
if ( empty( $messages ) && $existing_count > 0 ) {
|
|
return new WP_REST_Response(
|
|
[
|
|
'updated' => false,
|
|
'message_count' => $existing_count,
|
|
'skipped' => 'empty_overwrite_blocked',
|
|
],
|
|
200,
|
|
);
|
|
}
|
|
|
|
$updated = $manager->update_messages( $session_id, $messages );
|
|
if ( ! $updated ) {
|
|
return new WP_Error(
|
|
'message_update_failed',
|
|
__(
|
|
'Failed to update conversation messages.',
|
|
'wp-agentic-writer',
|
|
),
|
|
[ 'status' => 500 ],
|
|
);
|
|
}
|
|
|
|
return new WP_REST_Response(
|
|
[ 'updated' => true, 'message_count' => count( $messages ) ],
|
|
200,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle link conversation to post request.
|
|
*
|
|
* @since 0.1.4
|
|
* @param WP_REST_Request $request REST request.
|
|
* @return WP_REST_Response|WP_Error
|
|
*/
|
|
public function handle_link_conversation_to_post( $request ) {
|
|
$params = $request->get_json_params();
|
|
$session_id = sanitize_text_field( $request->get_param( 'session_id' ) );
|
|
$manager = WP_Agentic_Writer_Conversation_Manager::get_instance();
|
|
|
|
// First verify user has access to this session (before linking to post).
|
|
if ( ! $manager->current_user_can_access( $session_id ) ) {
|
|
return new WP_Error(
|
|
'forbidden',
|
|
__(
|
|
'You do not have access to this conversation.',
|
|
'wp-agentic-writer',
|
|
),
|
|
[ 'status' => 403 ],
|
|
);
|
|
}
|
|
|
|
$session = $manager->get_session( $session_id );
|
|
|
|
if ( ! $session ) {
|
|
return new WP_Error(
|
|
'not_found',
|
|
__( 'Conversation not found.', 'wp-agentic-writer' ),
|
|
[ 'status' => 404 ],
|
|
);
|
|
}
|
|
|
|
$post_id = isset( $params['post_id'] ) ? (int) $params['post_id'] : 0;
|
|
|
|
if ( $post_id <= 0 ) {
|
|
return new WP_Error(
|
|
'invalid_post',
|
|
__( 'Valid post ID is required.', 'wp-agentic-writer' ),
|
|
[ 'status' => 400 ],
|
|
);
|
|
}
|
|
|
|
// Verify post exists and user can edit.
|
|
if ( ! current_user_can( 'edit_post', $post_id ) ) {
|
|
return new WP_Error(
|
|
'permission_denied',
|
|
__(
|
|
'You do not have permission to edit this post.',
|
|
'wp-agentic-writer',
|
|
),
|
|
[ 'status' => 403 ],
|
|
);
|
|
}
|
|
|
|
$manager->link_to_post( $session_id, $post_id );
|
|
|
|
$updated_session = $manager->get_session( $session_id );
|
|
|
|
return new WP_REST_Response(
|
|
[
|
|
'linked' => true,
|
|
'post_id' => $post_id,
|
|
'session' => $updated_session,
|
|
],
|
|
200,
|
|
);
|
|
}
|
|
}
|