PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.8.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.8.2
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.2, at includes/Utils/AIUsage.php

171 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 'api_docs_ai',
54 ];
55
56 /**
57 * Record one successful AI invocation.
58 *
59 * @param string $feature One of {@see self::FEATURES}. Unknown keys are ignored.
60 * @param int $post_id Document id, or 0 when there is no reliable post (e.g. an
61 * unsaved doc or a pre-save glossary/FAQ generation) — the
62 * per-doc meta is then skipped, the site-wide total still bumps.
63 * @param string $sub Optional sub-bucket (e.g. an AI Edit action: improve/rewrite/…).
64 * @return void
65 */
66 public static function record( $feature, $post_id = 0, $sub = '' ) {
67 if ( ! in_array( $feature, self::FEATURES, true ) ) {
68 return;
69 }
70
71 // Site-wide aggregate.
72 $usage = get_option( self::OPTION_KEY, [] );
73 if ( ! is_array( $usage ) ) {
74 $usage = [];
75 }
76 $usage[ $feature ] = (int) ( $usage[ $feature ] ?? 0 ) + 1;
77
78 if ( $sub !== '' ) {
79 $sub = sanitize_key( $sub );
80 if ( $sub !== '' ) {
81 $bucket = $feature . '_actions';
82 $usage[ $bucket ][ $sub ] = (int) ( $usage[ $bucket ][ $sub ] ?? 0 ) + 1;
83 }
84 }
85
86 update_option( self::OPTION_KEY, $usage, true );
87
88 // Per-document breakdown.
89 if ( $post_id > 0 ) {
90 $meta = get_post_meta( $post_id, self::META_KEY, true );
91 if ( ! is_array( $meta ) ) {
92 $meta = [];
93 }
94 $meta[ $feature ] = (int) ( $meta[ $feature ] ?? 0 ) + 1;
95 update_post_meta( $post_id, self::META_KEY, $meta );
96 }
97 }
98
99 /**
100 * Normalised, fixed-schema snapshot for the wpinsight collector. Every feature key
101 * is always present (default 0); action sub-maps are included when set.
102 *
103 * Sub-map handling:
104 * - `ai_edit_actions` — shipped as-is (improve/rewrite/shorten/…).
105 * - `write_with_ai_modes` — the raw `write_with_ai_actions` map is rolled up into
106 * the three user-facing source modes (prompt / source / git) so wpinsight reads a
107 * stable, human-meaningful breakdown regardless of internal action names. Always
108 * present so the schema is stable.
109 * - `sample_docs_actions` — per content-type (docs/faq/product_faq), when set.
110 * - `ai_suggest_terms_actions`— per taxonomy (doc_category/doc_tag/glossaries), when set.
111 *
112 * @return array<string,int|array<string,int>>
113 */
114 public static function snapshot() {
115 $usage = get_option( self::OPTION_KEY, [] );
116 $usage = is_array( $usage ) ? $usage : [];
117
118 $out = [];
119 foreach ( self::FEATURES as $key ) {
120 $out[ $key ] = (int) ( $usage[ $key ] ?? 0 );
121 }
122
123 if ( ! empty( $usage['ai_edit_actions'] ) && is_array( $usage['ai_edit_actions'] ) ) {
124 $out['ai_edit_actions'] = array_map( 'intval', $usage['ai_edit_actions'] );
125 }
126
127 // Roll the raw Write-with-AI actions up into the three source modes the user picks.
128 $out['write_with_ai_modes'] = self::write_with_ai_modes( $usage['write_with_ai_actions'] ?? [] );
129
130 if ( ! empty( $usage['sample_docs_actions'] ) && is_array( $usage['sample_docs_actions'] ) ) {
131 $out['sample_docs_actions'] = array_map( 'intval', $usage['sample_docs_actions'] );
132 }
133
134 if ( ! empty( $usage['ai_suggest_terms_actions'] ) && is_array( $usage['ai_suggest_terms_actions'] ) ) {
135 $out['ai_suggest_terms_actions'] = array_map( 'intval', $usage['ai_suggest_terms_actions'] );
136 }
137
138 return $out;
139 }
140
141 /**
142 * Normalise the raw `write_with_ai_actions` sub-map (keyed by internal action names
143 * from the Write-with-AI REST endpoint) into the three source modes surfaced in the
144 * AI Studio "Write Documentation" modal: Prompt | From Source | From Git.
145 *
146 * - `from-source` → source
147 * - `from-git` → git
148 * - generate-doc / generate-outline / expand-outline (and any future prompt-driven
149 * action) → prompt
150 *
151 * @param array<string,int> $actions
152 * @return array{prompt:int,source:int,git:int}
153 */
154 protected static function write_with_ai_modes( $actions ) {
155 $modes = [ 'prompt' => 0, 'source' => 0, 'git' => 0 ];
156
157 foreach ( (array) $actions as $action => $count ) {
158 $count = (int) $count;
159 if ( 'from-source' === $action ) {
160 $modes['source'] += $count;
161 } elseif ( 'from-git' === $action ) {
162 $modes['git'] += $count;
163 } else {
164 $modes['prompt'] += $count;
165 }
166 }
167
168 return $modes;
169 }
170 }
171