Conversationrepository - Source Excerpt 01
Back to Conversationrepository
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiralist-workspace/includes/Domain/ConversationRepository.php so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Domain/ConversationRepository.php
<?php
namespace SpiralistWorkspace\Domain;
use SpiralistWorkspace\Installer;
if (!defined('ABSPATH')) {
exit;
}
/**
* Persists prompt-based conversations and messages.
*/
class ConversationRepository
{
/**
* Creates a conversation row.
*/
public function create(array $data): int
{
global $wpdb;
$now = gmdate('Y-m-d H:i:s');
$wpdb->insert(
Installer::table('conversations'),
[
'prompt_post_id' => (int) ($data['prompt_post_id'] ?? 0),
'owner_user_id' => (int) ($data['owner_user_id'] ?? 0),
'title' => sanitize_text_field((string) ($data['title'] ?? 'Untitled Conversation')),
'visibility' => sanitize_key((string) ($data['visibility'] ?? 'private')),
'created_utc' => $now,
'updated_utc' => $now,
'archived_utc' => null,
'openai_reference' => !empty($data['openai_reference']) ? sanitize_text_field((string) $data['openai_reference']) : null,
'conversation_settings_json' => wp_json_encode((array) ($data['conversation_settings_json'] ?? []), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
],
['%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s']
);
return (int) $wpdb->insert_id;
}
/**
* Updates the latest runtime reference and timestamp.
*/
public function update_openai_reference(int $conversation_id, ?string $reference): void
{
global $wpdb;
$reference = trim((string) $reference);
$wpdb->update(
Installer::table('conversations'),
[
'openai_reference' => $reference !== '' ? sanitize_text_field($reference) : null,
'updated_utc' => gmdate('Y-m-d H:i:s'),
],
[
'id' => $conversation_id,
],
['%s', '%s'],
['%d']
);
}
/**
* Decodes the stored conversation settings.
*/
public function decode_settings(array $conversation): array
{
$decoded = json_decode((string) ($conversation['conversation_settings_json'] ?? ''), true);
return is_array($decoded) ? $decoded : [];
}
/**
* Updates conversation settings and timestamp.
*/
public function update_settings(int $conversation_id, array $settings): void
{
global $wpdb;
$wpdb->update(
Installer::table('conversations'),
[
'conversation_settings_json' => wp_json_encode($settings, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
'updated_utc' => gmdate('Y-m-d H:i:s'),
],
[
'id' => $conversation_id,
],
['%s', '%s'],
['%d']
);
}
/**
* Returns the active memory size and checkpoint state for a conversation.
*/
public function memory_status(array $conversation, array $messages, int $threshold_tokens): array
{
$settings = $this->decode_settings($conversation);
$memory_summary = trim((string) ($settings['memory_summary'] ?? ''));
$memory_messages = $this->messages_for_memory($messages);
$total_messages = count($memory_messages);
$checkpoint_message_count = min(
$total_messages,
max(0, (int) ($settings['memory_summary_message_count'] ?? 0))
);
$active_messages = array_slice($memory_messages, $checkpoint_message_count);
$threshold_tokens = max(1, $threshold_tokens);
$estimated_active_tokens = $this->estimate_messages_tokens($active_messages) + $this->estimate_text_tokens($memory_summary);
$estimated_total_tokens = $this->estimate_messages_tokens($memory_messages) + $this->estimate_text_tokens($memory_summary);
$percent = (int) round(($estimated_active_tokens / $threshold_tokens) * 100);
return [
'threshold_tokens' => $threshold_tokens,
'estimated_active_tokens' => $estimated_active_tokens,
'estimated_total_tokens' => $estimated_total_tokens,
'percent_of_threshold' => max(0, min(200, $percent)),
'warning_level' => $estimated_active_tokens >= $threshold_tokens
? 'checkpoint'
: ($estimated_active_tokens >= (int) floor($threshold_tokens * 0.8) ? 'soon' : 'ok'),
'needs_checkpoint' => $estimated_active_tokens >= $threshold_tokens,
'has_memory_summary' => $memory_summary !== '',
'memory_summary_updated_utc' => (string) ($settings['memory_summary_updated_utc'] ?? ''),
'memory_summary_message_count' => $checkpoint_message_count,
'memory_summary_run_id' => (int) ($settings['memory_summary_run_id'] ?? 0),
'total_messages' => $total_messages,
'messages_since_summary' => count($active_messages),
];
}
/**
* Keeps user-visible turns in the memory budget.
*/
public function messages_for_memory(array $messages): array
{
return array_values(
array_filter(
$messages,
static function ($message): bool {
if (!is_array($message)) {
return false;
}
$role = sanitize_key((string) ($message['role'] ?? ''));
return in_array($role, ['user', 'assistant'], true)
&& trim((string) ($message['content'] ?? '')) !== '';
}
)
);
}
/**
* Estimates token pressure with a conservative character-based heuristic.
*/
public function estimate_messages_tokens(array $messages): int
{
$characters = 0;
foreach ($this->messages_for_memory($messages) as $message) {
$characters += strlen((string) ($message['role'] ?? ''));
$characters += strlen(wp_strip_all_tags((string) ($message['content'] ?? '')));
$characters += 8;
}
return (int) ceil($characters / 4);
}
/**
* Estimates tokens for a single text value.
*/
public function estimate_text_tokens(string $text): int
{
$text = trim(wp_strip_all_tags($text));
return $text === '' ? 0 : (int) ceil(strlen($text) / 4);
}
/**
* Adds a message to a conversation.
*/
public function add_message(int $conversation_id, string $role, string $content, ?string $openai_reference = null, array $metadata = []): int
{
global $wpdb;
$wpdb->insert(
Installer::table('conversation_messages'),
[
'conversation_id' => $conversation_id,
'role' => sanitize_key($role),
'content' => wp_kses_post($content),
'created_utc' => gmdate('Y-m-d H:i:s'),
'openai_reference' => $openai_reference ? sanitize_text_field($openai_reference) : null,
'metadata_json' => !empty($metadata) ? wp_json_encode($metadata, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : null,
],
['%d', '%s', '%s', '%s', '%s', '%s']
);
$this->touch($conversation_id);
return (int) $wpdb->insert_id;
}
/**
* Retrieves a conversation.
*/
public function get(int $conversation_id): array
{
global $wpdb;
$sql = $wpdb->prepare(
'SELECT * FROM ' . Installer::table('conversations') . ' WHERE id = %d LIMIT 1',
$conversation_id
);
$row = $wpdb->get_row($sql, ARRAY_A);
return is_array($row) ? $row : [];
}
/**
* Lists conversations for a user.
*/
public function list_for_user(int $user_id, int $limit = 20): array
{
global $wpdb;
$sql = $wpdb->prepare(
'SELECT * FROM ' . Installer::table('conversations') . ' WHERE owner_user_id = %d ORDER BY updated_utc DESC LIMIT %d',
$user_id,
max(1, $limit)
);
$rows = $wpdb->get_results($sql, ARRAY_A);
return is_array($rows) ? $rows : [];
}
/**
* Lists messages for a conversation.
*/
public function get_messages(int $conversation_id): array
{
global $wpdb;
$sql = $wpdb->prepare(
'SELECT * FROM ' . Installer::table('conversation_messages') . ' WHERE conversation_id = %d ORDER BY created_utc ASC',
$conversation_id
);
$rows = $wpdb->get_results($sql, ARRAY_A);
return is_array($rows) ? $rows : [];
}
/**