PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.8.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.8.0
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Utils / AIUsage.php

AIUsage.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.8.0, at includes/Utils/AIUsage.php

170 lines 5.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\Utils;
4
5 /**
6 * Exit if accessed directly
7 */
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Lightweight usage counter for AI features.
14 *
15 * Each successful AI invocation is recorded in two places:
16 * - a site-wide autoloaded option ({@see self::OPTION_KEY}) holding lifetime totals
17 * per feature (plus an `*_actions` sub-map for features with action variants, e.g.
18 * AI Edit's improve/rewrite/...). This is what the wpinsight collector ships.
19 * - a per-doc post meta ({@see self::META_KEY}) when a valid post id is available, so
20 * on-site reporting can later break usage down per document.
21 *
22 * Writes are a plain read-modify-write (not atomic) — the same trade-off the existing
23 * `betterdocs_analytics` increments make; acceptable for low-stakes usage telemetry.
24 *
25 * @since 4.5.4
26 */
27 class AIUsage {
28 /**
29 * Autoloaded option holding site-wide lifetime totals.
30 */
31 const OPTION_KEY = 'betterdocs_ai_usage';
32
33 /**
34 * Per-document usage counts.
35 */
36 const META_KEY = '_betterdocs_ai_usage';
37
38 /**
39 * Canonical feature keys. Kept fixed so the wpinsight payload schema is stable —
40 * every key is always present (default 0) even before a feature is ever used.
41 *
42 * @var string[]
43 */
44 const FEATURES = [
45 'write_with_ai',
46 'ai_edit',
47 'article_summary',
48 'quality_score',
49 'glossaries_write_with_ai',
50 'faq_write_with_ai',
51 'sample_docs',
52 'ai_suggest_terms',
53 ];
54
55 /**
56 * Record one successful AI invocation.
57 *
58 * @param string $feature One of {@see self::FEATURES}. Unknown keys are ignored.
59 * @param int $post_id Document id, or 0 when there is no reliable post (e.g. an
60 * unsaved doc or a pre-save glossary/FAQ generation) — the
61 * per-doc meta is then skipped, the site-wide total still bumps.
62 * @param string $sub Optional sub-bucket (e.g. an AI Edit action: improve/rewrite/…).
63 * @return void
64 */
65 public static function record( $feature, $post_id = 0, $sub = '' ) {
66 if ( ! in_array( $feature, self::FEATURES, true ) ) {
67 return;
68 }
69
70 // Site-wide aggregate.
71 $usage = get_option( self::OPTION_KEY, [] );
72 if ( ! is_array( $usage ) ) {
73 $usage = [];
74 }
75 $usage[ $feature ] = (int) ( $usage[ $feature ] ?? 0 ) + 1;
76
77 if ( $sub !== '' ) {
78 $sub = sanitize_key( $sub );
79 if ( $sub !== '' ) {
80 $bucket = $feature . '_actions';
81 $usage[ $bucket ][ $sub ] = (int) ( $usage[ $bucket ][ $sub ] ?? 0 ) + 1;
82 }
83 }
84
85 update_option( self::OPTION_KEY, $usage, true );
86
87 // Per-document breakdown.
88 if ( $post_id > 0 ) {
89 $meta = get_post_meta( $post_id, self::META_KEY, true );
90 if ( ! is_array( $meta ) ) {
91 $meta = [];
92 }
93 $meta[ $feature ] = (int) ( $meta[ $feature ] ?? 0 ) + 1;
94 update_post_meta( $post_id, self::META_KEY, $meta );
95 }
96 }
97
98 /**
99 * Normalised, fixed-schema snapshot for the wpinsight collector. Every feature key
100 * is always present (default 0); action sub-maps are included when set.
101 *
102 * Sub-map handling:
103 * - `ai_edit_actions` — shipped as-is (improve/rewrite/shorten/…).
104 * - `write_with_ai_modes` — the raw `write_with_ai_actions` map is rolled up into
105 * the three user-facing source modes (prompt / source / git) so wpinsight reads a
106 * stable, human-meaningful breakdown regardless of internal action names. Always
107 * present so the schema is stable.
108 * - `sample_docs_actions` — per content-type (docs/faq/product_faq), when set.
109 * - `ai_suggest_terms_actions`— per taxonomy (doc_category/doc_tag/glossaries), when set.
110 *
111 * @return array<string,int|array<string,int>>
112 */
113 public static function snapshot() {
114 $usage = get_option( self::OPTION_KEY, [] );
115 $usage = is_array( $usage ) ? $usage : [];
116
117 $out = [];
118 foreach ( self::FEATURES as $key ) {
119 $out[ $key ] = (int) ( $usage[ $key ] ?? 0 );
120 }
121
122 if ( ! empty( $usage['ai_edit_actions'] ) && is_array( $usage['ai_edit_actions'] ) ) {
123 $out['ai_edit_actions'] = array_map( 'intval', $usage['ai_edit_actions'] );
124 }
125
126 // Roll the raw Write-with-AI actions up into the three source modes the user picks.
127 $out['write_with_ai_modes'] = self::write_with_ai_modes( $usage['write_with_ai_actions'] ?? [] );
128
129 if ( ! empty( $usage['sample_docs_actions'] ) && is_array( $usage['sample_docs_actions'] ) ) {
130 $out['sample_docs_actions'] = array_map( 'intval', $usage['sample_docs_actions'] );
131 }
132
133 if ( ! empty( $usage['ai_suggest_terms_actions'] ) && is_array( $usage['ai_suggest_terms_actions'] ) ) {
134 $out['ai_suggest_terms_actions'] = array_map( 'intval', $usage['ai_suggest_terms_actions'] );
135 }
136
137 return $out;
138 }
139
140 /**
141 * Normalise the raw `write_with_ai_actions` sub-map (keyed by internal action names
142 * from the Write-with-AI REST endpoint) into the three source modes surfaced in the
143 * AI Studio "Write Documentation" modal: Prompt | From Source | From Git.
144 *
145 * - `from-source` → source
146 * - `from-git` → git
147 * - generate-doc / generate-outline / expand-outline (and any future prompt-driven
148 * action) → prompt
149 *
150 * @param array<string,int> $actions
151 * @return array{prompt:int,source:int,git:int}
152 */
153 protected static function write_with_ai_modes( $actions ) {
154 $modes = [ 'prompt' => 0, 'source' => 0, 'git' => 0 ];
155
156 foreach ( (array) $actions as $action => $count ) {
157 $count = (int) $count;
158 if ( 'from-source' === $action ) {
159 $modes['source'] += $count;
160 } elseif ( 'from-git' === $action ) {
161 $modes['git'] += $count;
162 } else {
163 $modes['prompt'] += $count;
164 }
165 }
166
167 return $modes;
168 }
169 }
170