400 ] ); } $result = AIService::run( 'brief', self::compose_message( $brief, trim( $instruction ) ) ); if ( is_wp_error( $result ) ) { return $result; } $text = self::clean( (string) ( $result['assistant_message'] ?? '' ) ); if ( '' === $text ) { return new \WP_Error( 'ai_brief_empty', __( 'The assistant did not return an improved brief. Please try again.', 'better-payment' ), [ 'status' => 502 ] ); } return [ 'brief' => $text, 'usage' => $result['usage'] ?? [], ]; } /** * Build the user message: the current brief, plus the improvement instruction * when there is one. Labelled plainly so the model knows which part is the * brief to rewrite and which is the direction to follow. * * @param string $brief * @param string $instruction * @return string */ private static function compose_message( string $brief, string $instruction ): string { if ( '' === $instruction ) { return "Current brief:\n\n" . $brief . "\n\nRewrite this brief so it is more vivid, specific and complete, following the rules above."; } return "Current brief:\n\n" . $brief . "\n\nImprove the brief following this instruction: " . $instruction; } /** * Strip machine wrapping from a reply meant to be bare prose. * * The model is told to return the brief and nothing else, but occasionally * wraps it in a code fence or surrounding quotes. Remove those so they don't * land in the user's editable brief box. This unwraps structure; it does not * rewrite the writing. * * @param string $text * @return string */ private static function clean( string $text ): string { $text = trim( $text ); // A fully fenced reply → keep only the fence's contents. if ( preg_match( '/^```[a-z]*\s*\n(.*?)\n?```$/is', $text, $matches ) ) { $text = trim( $matches[1] ); } // Surrounding matched quotes the model sometimes adds around the whole brief. if ( strlen( $text ) >= 2 ) { $first = $text[0]; $last = $text[ strlen( $text ) - 1 ]; if ( ( '"' === $first && '"' === $last ) || ( "'" === $first && "'" === $last ) ) { $text = trim( substr( $text, 1, -1 ) ); } } return $text; } }