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
61 changes: 48 additions & 13 deletions inc/API.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ public function register_routes() {
'type' => 'string',
'enum' => [ 'stream', 'background' ],
],
'page_url' => [
'required' => false,
'type' => 'string',
],
],
'callback' => [ $this, 'send_chat' ],
'permission_callback' => function ( $request ) {
Expand Down Expand Up @@ -2126,6 +2130,7 @@ public function send_chat( $request ) {
'context' => $prepared['context'],
'is_test' => $is_test,
'source_post_ids' => $this->source_post_ids,
'page' => isset( $prepared['page'] ) ? $prepared['page'] : null,
],
5 * MINUTE_IN_SECONDS
);
Expand Down Expand Up @@ -2175,21 +2180,25 @@ public function send_chat( $request ) {
* stored under a run token that `get_chat` reads back, keeping the widget's
* send-then-poll contract intact without an OpenAI background run.
*
* @param array{thread_id:string,message:string,context:string} $prepared Prepared turn.
* @param bool $is_test Admin live-preview chat.
* @param int|null $record_id Existing conversation record.
* @param array{thread_id:string,message:string,context:string,page:array<string, mixed>|null} $prepared Prepared turn.
* @param bool $is_test Admin live-preview chat.
* @param int|null $record_id Existing conversation record.
*
* @return \WP_REST_Response
*/
private function send_chat_connect( $prepared, $is_test, $record_id ) {
$result = Hyve_Connect::instance()->chat(
[
'message' => $prepared['message'],
'thread_id' => '' !== $prepared['thread_id'] ? $prepared['thread_id'] : null,
'settings' => Hyve_Connect::chat_settings(),
'stream' => false,
]
);
$payload = [
'message' => $prepared['message'],
'thread_id' => '' !== $prepared['thread_id'] ? $prepared['thread_id'] : null,
'settings' => Hyve_Connect::chat_settings(),
'stream' => false,
];

if ( ! empty( $prepared['page'] ) ) {
$payload['page'] = $prepared['page'];
}

$result = Hyve_Connect::instance()->chat( $payload );

if ( is_wp_error( $result ) ) {
return rest_ensure_response(
Expand Down Expand Up @@ -2290,7 +2299,7 @@ private function get_chat_connect( $request ) {
*
* @param \WP_REST_Request<array<string, mixed>> $request Request.
*
* @return array{thread_id:string,message:string,context:string}|\WP_Error
* @return array{thread_id:string,message:string,context:string,page:array<string, mixed>|null}|\WP_Error
*/
private function prepare_chat( $request ) {
$message = $request->get_param( 'message' );
Expand All @@ -2299,6 +2308,8 @@ private function prepare_chat( $request ) {
return new \WP_Error( 'missing_message', __( 'Message was flagged.', 'hyve-lite' ) );
}

$page = Page_Context::instance()->for_request( $request );

// Connect mode: moderation, embedding, retrieval, and thread minting all
// happen server-side inside hyve-chat, so there is no local prep. The
// platform mints the thread id on turn 1 and echoes it back.
Expand All @@ -2310,6 +2321,7 @@ private function prepare_chat( $request ) {
'thread_id' => $thread_id ? $thread_id : '',
'message' => $message,
'context' => '',
'page' => $page ? Page_Context::instance()->payload( $page ) : null,
];
}

Expand All @@ -2323,7 +2335,12 @@ private function prepare_chat( $request ) {
$record_id = $request->get_param( 'record_id' );
$record_id = $record_id ? $record_id : null;
$retrieval_query = $this->build_retrieval_query( $message, $record_id, $request->get_param( 'thread_id' ) );
$message_vector = $openai->create_embeddings( $retrieval_query );

if ( $page && '' !== $page['title'] ) {
$retrieval_query .= "\n" . $page['title'];
}

$message_vector = $openai->create_embeddings( $retrieval_query );

if ( is_wp_error( $message_vector ) ) {
return new \WP_Error( 'no_embeddings', __( 'No embeddings found.', 'hyve-lite' ) );
Expand Down Expand Up @@ -2358,6 +2375,23 @@ private function prepare_chat( $request ) {

$article_context = $this->search_knowledge_base( $message_vector, $similarity_score_threshold );

if ( $page ) {
$article_context .= Page_Context::instance()->context_block( $page, $article_context );
}

// The visitor is already on this page; linking them to it as a
// source would be noise.
if ( $page && $page['id'] ) {
$this->source_post_ids = array_values(
array_filter(
$this->source_post_ids,
function ( $source_id ) use ( $page ) {
return intval( $source_id ) !== $page['id'];
}
)
);
}

$hash = hash( 'md5', strtolower( $message ) );
// TTL must outlast the slowest reply (streaming can run up to the 120s
// cURL cap) so the embedding is still available when hyve_chat_response
Expand All @@ -2368,6 +2402,7 @@ private function prepare_chat( $request ) {
'thread_id' => $thread_id,
'message' => $message,
'context' => $article_context,
'page' => null,
];
}

Expand Down
20 changes: 20 additions & 0 deletions inc/DB_Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,26 @@ public function get_post_id( $id ) {
return $wpdb->get_var( $wpdb->prepare( 'SELECT post_id FROM %i WHERE id = %d', $this->table_name, $id ) );
}

/**
* Get the processed chunks of a source post.
*
* Used to pin the visitor's current page into the chat context. Rows stay
* in this table in both storage backends (Qdrant only holds the vectors),
* so this works regardless of where embeddings live.
*
* @since 1.5.0
*
* @param int $post_id The source post ID.
* @param int $limit Maximum chunks to return.
*
* @return array<object{post_title: string, post_content: string, token_count: string}>
*/
public function get_chunks_by_post_id( $post_id, $limit = 20 ) {
global $wpdb;

return $wpdb->get_results( $wpdb->prepare( 'SELECT post_title, post_content, token_count FROM %i WHERE post_id = %d AND post_status = %s ORDER BY id ASC LIMIT %d', $this->table_name, $post_id, 'processed', $limit ) );
}

/**
* Update storage of all rows.
*
Expand Down
4 changes: 2 additions & 2 deletions inc/Hyve_Connect.php
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ public function get_quota() {
* The terminal `job_complete` payload (reply, answered, sources, thread_id,
* usage) is returned to the caller.
*
* @param array<string, mixed> $payload hyve-chat input ({message,thread_id,settings,stream}).
* @param array<string, mixed> $payload hyve-chat input ({message,thread_id,settings,stream,page?}).
* @param callable(string, array<string, mixed>): void $on_event Receives each intra-stream event (delta, kb_state, sources).
*
* @return array<string, mixed>|\WP_Error The job_complete payload, or an error.
Expand Down Expand Up @@ -437,7 +437,7 @@ public function stream_chat( $payload, $on_event ) {
* Same envelope as the stream, minus `delta` events; returns the terminal
* `job_complete` payload.
*
* @param array<string, mixed> $payload hyve-chat input ({message,thread_id,settings,stream:false}).
* @param array<string, mixed> $payload hyve-chat input ({message,thread_id,settings,stream:false,page?}).
*
* @return array<string, mixed>|\WP_Error
*/
Expand Down
Loading
Loading