PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.3.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.3.1
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 66 releases
better-payment / includes / AI / Services / ConversationManager.php

ConversationManager.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.3.1, at includes/AI/Services/ConversationManager.php

116 lines 3.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\AI\Services;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 /**
10 * Per-campaign AI conversation history.
11 *
12 * Stored as JSON in the `_bpc_ai_history` post meta on the campaign, capped to
13 * the most recent turns so prompts stay small and meta stays bounded. For a
14 * brand-new (unsaved) campaign, history lives only in the client until first
15 * save; the server simply receives it in the request.
16 */
17 class ConversationManager {
18
19 const META_KEY = '_bpc_ai_history';
20 const MAX_TURNS = 40;
21
22 /**
23 * Load stored history for a campaign.
24 *
25 * @return array<int, array{role: string, content: string}>
26 */
27 public static function get( int $campaign_id ): array {
28 if ( $campaign_id <= 0 ) {
29 return [];
30 }
31 $raw = get_post_meta( $campaign_id, self::META_KEY, true );
32 if ( is_string( $raw ) && '' !== $raw ) {
33 $decoded = json_decode( $raw, true );
34 if ( is_array( $decoded ) ) {
35 return self::sanitize( $decoded );
36 }
37 }
38 return [];
39 }
40
41 /**
42 * Append a user + assistant turn and persist (capped).
43 *
44 * @param int $campaign_id
45 * @param string $user_message
46 * @param string $assistant_message
47 * @return array<int, array> The full, capped history after appending.
48 */
49 public static function append( int $campaign_id, string $user_message, string $assistant_message ): array {
50 $history = self::get( $campaign_id );
51 $history[] = [ 'role' => 'user', 'content' => $user_message ];
52 $history[] = [ 'role' => 'assistant', 'content' => $assistant_message ];
53 $history = array_slice( $history, - self::MAX_TURNS );
54
55 if ( $campaign_id > 0 ) {
56 update_post_meta( $campaign_id, self::META_KEY, wp_slash( wp_json_encode( $history ) ) );
57 }
58 return $history;
59 }
60
61 /**
62 * Replace the whole stored history with the given messages (sanitized +
63 * capped). The client owns the authoritative message list, so this is how it
64 * syncs the conversation to the campaign — in particular the first generate
65 * turn, which is produced while the campaign is still unsaved and therefore
66 * cannot be appended during the /ai/generate request.
67 *
68 * @param int $campaign_id
69 * @param array $messages Raw [ [ role, content ], ... ] from the client.
70 * @return array<int, array{role: string, content: string}> The stored history.
71 */
72 public static function replace( int $campaign_id, array $messages ): array {
73 $history = self::sanitize( $messages );
74 if ( $campaign_id <= 0 ) {
75 return $history;
76 }
77 if ( empty( $history ) ) {
78 delete_post_meta( $campaign_id, self::META_KEY );
79 return [];
80 }
81 update_post_meta( $campaign_id, self::META_KEY, wp_slash( wp_json_encode( $history ) ) );
82 return $history;
83 }
84
85 /**
86 * Clear stored history for a campaign.
87 */
88 public static function clear( int $campaign_id ): bool {
89 if ( $campaign_id <= 0 ) {
90 return false;
91 }
92 return delete_post_meta( $campaign_id, self::META_KEY );
93 }
94
95 /**
96 * Normalise a history array to role/content pairs with safe strings.
97 *
98 * @param array $history
99 * @return array<int, array{role: string, content: string}>
100 */
101 public static function sanitize( array $history ): array {
102 $clean = [];
103 foreach ( $history as $turn ) {
104 if ( ! is_array( $turn ) ) {
105 continue;
106 }
107 $role = ( 'assistant' === ( $turn['role'] ?? '' ) ) ? 'assistant' : 'user';
108 $clean[] = [
109 'role' => $role,
110 'content' => sanitize_textarea_field( (string) ( $turn['content'] ?? '' ) ),
111 ];
112 }
113 return array_slice( $clean, - self::MAX_TURNS );
114 }
115 }
116