ai_topics ?? '' ) ), 'strlen' ) ); } /** * Marks for urgent leads, orders, job applications and likely spam. * * @param object $lead Lead. * @return string HTML. */ public static function marks( $lead ) { $marks = array(); if ( ! empty( $lead->ai_urgent ) && in_array( $lead->status, array( 'new', 'contacted' ), true ) ) { $marks[] = '' . esc_html__( 'Urgent', 'dealroot' ) . ''; } $kinds = array( 'order' => __( 'Order', 'dealroot' ), 'job' => __( 'Job application', 'dealroot' ), 'spam' => __( 'Looks like spam', 'dealroot' ), ); $kind = (string) ( $lead->ai_kind ?? '' ); if ( isset( $kinds[ $kind ] ) && ( 'spam' !== $kind || 'spam' !== $lead->status ) ) { $marks[] = '' . esc_html( $kinds[ $kind ] ) . ''; } if ( '' !== (string) ( $lead->privacy_request ?? '' ) ) { $marks[] = '' . esc_html__( 'Personal data', 'dealroot' ) . ''; } if ( ! empty( $lead->ai_abusive ) ) { $marks[] = '' . esc_html__( 'Insulting', 'dealroot' ) . ''; } return implode( '', $marks ); } /** * The summary above the fields of a lead. * * @param object $lead Lead. */ public static function summary( $lead ) { $state = (string) ( $lead->ai_state ?? '' ); if ( 'done' === $state && '' !== (string) $lead->ai_summary ) { ?>

ai_summary ); ?>

'; echo wp_kses( $marks, Kses::screen() ); foreach ( $topics as $topic ) { echo '' . esc_html( $topic ) . ''; } echo '

'; } self::confirmation( $lead ); if ( 'spam' === $lead->status && 'spam' === (string) $lead->ai_kind ) { echo '

' . esc_html__( 'The AI assistant moved this enquiry to spam. If it is a real one, set its status back.', 'dealroot' ) . '

'; } ?>
' . esc_html__( 'Sorted out as spam without the AI assistant: it has several links or the words of mass advertising. If it is a real enquiry, set its status back.', 'dealroot' ) . '

'; return; } if ( '' === $state && Ai::enabled() && strtotime( $lead->created_at . ' UTC' ) > time() - DAY_IN_SECONDS ) { echo '

' . esc_html__( 'The AI assistant is reading the enquiry…', 'dealroot' ) . '

'; } } /** * Budget, date, quantity and place the assistant read in the enquiry. * * @param object $lead Lead. */ private static function facts( $lead ) { $facts = array(); if ( null !== ( $lead->ai_budget ?? null ) && '' !== (string) $lead->ai_budget ) { $facts[ __( 'Budget', 'dealroot' ) ] = Format::money( (float) $lead->ai_budget, (string) $lead->ai_currency ); } $labels = array( 'when' => __( 'When', 'dealroot' ), 'quantity' => __( 'How many', 'dealroot' ), 'place' => __( 'Where', 'dealroot' ), ); foreach ( Ai::details( $lead ) as $key => $value ) { if ( isset( $labels[ $key ] ) ) { $facts[ $labels[ $key ] ] = $value; } } if ( ! $facts ) { return; } echo '
'; foreach ( $facts as $label => $value ) { echo '
' . esc_html( $label ) . '
' . esc_html( $value ) . '
'; } echo '
'; } /** * The confirmation the person got right after the enquiry. * * @param object $lead Lead. */ private static function confirmation( $lead ) { $confirmation = Ai::confirmation( $lead ); if ( ! $confirmation ) { return; } ?>
ai_confirmed_at . ' UTC' ) ) ) ); ?>

array() ) ); ?>

ai_lang ); ?>

> array() ) ); ?>
__( 'Only an administrator can change this.', 'dealroot' ) ), 403 ); } if ( ! empty( $_POST['remove'] ) ) { $result = Ai::remove_key(); } else { $provider = isset( $_POST['provider'] ) ? sanitize_key( wp_unslash( $_POST['provider'] ) ) : ''; $model = isset( $_POST['model'] ) ? (string) preg_replace( '/[^A-Za-z0-9._:\/-]/', '', wp_unslash( $_POST['model'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Only the characters of model names are kept. $key = isset( $_POST['key'] ) ? trim( sanitize_text_field( wp_unslash( $_POST['key'] ) ) ) : ''; if ( ! isset( Ai::PROVIDERS[ $provider ] ) || '' === $key ) { wp_send_json_error( array( 'message' => __( 'Choose a provider and paste your API key.', 'dealroot' ) ) ); } $result = Ai::save_key( $provider, $model, $key ); } if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success(); } /** * Writes down a question asked by voice in the browser. */ public function ajax_listen() { check_ajax_referer( 'dealroot_ai_ask', 'nonce' ); if ( ! current_user_can( Admin::capability() ) ) { wp_send_json_error( null, 403 ); } $file = isset( $_FILES['audio']['tmp_name'] ) ? sanitize_text_field( wp_unslash( $_FILES['audio']['tmp_name'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Checked right below. if ( '' === $file || ! is_uploaded_file( $file ) || filesize( $file ) > 450000 ) { wp_send_json_error( array( 'message' => __( 'Voice messages can be up to two minutes long.', 'dealroot' ) ) ); } $text = Ai::transcribe( (string) file_get_contents( $file ), Ai::user_language() ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- A local upload. if ( is_wp_error( $text ) ) { wp_send_json_error( array( 'message' => $text->get_error_message() ) ); } if ( '' === $text ) { wp_send_json_error( array( 'message' => __( 'I could not hear anything in this voice message. Please record it again.', 'dealroot' ) ) ); } wp_send_json_success( array( 'text' => $text ) ); } /** * Reads a pasted conversation for the form of a new lead. */ public function ajax_conversation() { check_ajax_referer( 'dealroot_ai_conversation', 'nonce' ); if ( ! current_user_can( Admin::capability() ) ) { wp_send_json_error( null, 403 ); } // Addresses in angle brackets, as in "From: Name ", are no HTML tags. $text = isset( $_POST['text'] ) ? sanitize_textarea_field( (string) preg_replace( '/<([^<>\s]+@[^<>\s]+)>/', ' $1 ', (string) wp_unslash( $_POST['text'] ) ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized right after the brackets are taken off. if ( mb_strlen( $text ) < 10 ) { wp_send_json_error( array( 'message' => __( 'Paste the conversation first.', 'dealroot' ) ) ); } $read = \Dealroot\Telegram_Assistant::read( $text ); if ( is_wp_error( $read ) ) { wp_send_json_error( array( 'message' => $read->get_error_message() ) ); } $read['value'] = $read['budget'] > 0 ? number_format_i18n( $read['budget'], 2 ) : ''; wp_send_json_success( $read ); } /** * Translates an enquiry for the lead screen. */ public function ajax_translate() { check_ajax_referer( 'dealroot_ai_draft', 'nonce' ); if ( ! current_user_can( Admin::capability() ) ) { wp_send_json_error( null, 403 ); } $lead = Leads::get( isset( $_POST['lead'] ) ? absint( $_POST['lead'] ) : 0 ); if ( ! $lead ) { wp_send_json_error( array( 'message' => __( 'This lead was deleted, or the link is not complete.', 'dealroot' ) ), 404 ); } $text = Ai::translate( $lead ); if ( is_wp_error( $text ) ) { wp_send_json_error( array( 'message' => $text->get_error_message() ) ); } wp_send_json_success( array( 'text' => $text ) ); } /** * The draft button of the reply panel. * * @param object|null $lead Lead. */ public static function draft_button( $lead = null ) { if ( ! Ai::enabled() ) { return; } if ( $lead && ! empty( $lead->ai_abusive ) ) { echo '

' . esc_html( Ai::abusive_note() ) . '

'; return; } ?>
ai_lang : '' ) ) : ?>

__( 'This lead was deleted, or the link is not complete.', 'dealroot' ) ), 404 ); } $draft = Ai::draft( $lead, isset( $_POST['hint'] ) ? sanitize_text_field( wp_unslash( $_POST['hint'] ) ) : '' ); if ( is_wp_error( $draft ) ) { wp_send_json_error( array( 'message' => $draft->get_error_message() ) ); } wp_send_json_success( array( 'draft' => $draft ) ); } /** * A ticket for writing the draft word by word in the browser. */ public function ajax_ticket() { check_ajax_referer( 'dealroot_ai_draft', 'nonce' ); if ( ! current_user_can( Admin::capability() ) ) { wp_send_json_error( null, 403 ); } $lead = Leads::get( isset( $_POST['lead'] ) ? absint( $_POST['lead'] ) : 0 ); if ( ! $lead ) { wp_send_json_error( array( 'message' => __( 'This lead was deleted, or the link is not complete.', 'dealroot' ) ), 404 ); } $ticket = Ai::ticket( $lead, isset( $_POST['hint'] ) ? sanitize_text_field( wp_unslash( $_POST['hint'] ) ) : '' ); if ( is_wp_error( $ticket ) ) { wp_send_json_error( array( 'message' => $ticket->get_error_message() ) ); } wp_send_json_success( $ticket ); } /** * What customers ask for in a period: the topics with their leads, customers and revenue. * * @param string $period Period key. */ public static function topics_panel( $period ) { list( $from, $to ) = Reports::range( $period ); $topics = Ai::topics( $from, $to ); if ( count( $topics ) < 2 ) { return; } $max = max( wp_list_pluck( $topics, 'leads' ) ); ?>

$row ) : ?>
__( '%s of enquiries do not say when it is for, and your forms do not ask. Add a field for the date, and your first answer needs one question less.', 'dealroot' ), /* translators: %s: share of enquiries, e.g. "62 %" */ 'place' => __( '%s of enquiries do not say where it is for, and your forms do not ask. Add a field for the town or postcode, and your first answer needs one question less.', 'dealroot' ), ); ?>

__( 'Customers and ads', 'dealroot' ), 'icon' => 'dashicons-chart-bar', 'examples' => array( $questions[0] ?? __( 'Which channel brought the most customers this year?', 'dealroot' ), __( 'Which campaign had the best return on ad spend last month?', 'dealroot' ), __( 'What did an enquiry cost per campaign this month?', 'dealroot' ), __( 'Which cities do our customers come from?', 'dealroot' ), ), 'fill' => false, ), array( 'title' => __( 'Enquiries', 'dealroot' ), 'icon' => 'dashicons-email-alt', 'examples' => array( $questions[1] ?? __( 'How many enquiries did we get last month, per week?', 'dealroot' ), __( 'What do people ask about most?', 'dealroot' ), __( 'On which weekday do most enquiries come?', 'dealroot' ), $questions[3] ?? __( 'Which devices did visitors use this month?', 'dealroot' ), ), 'fill' => false, ), array( 'title' => __( 'Sales and speed', 'dealroot' ), 'icon' => 'dashicons-performance', 'examples' => array( $questions[2] ?? __( 'How fast did we answer in the last 30 days?', 'dealroot' ), __( 'Which enquiries are still waiting for an answer?', 'dealroot' ), __( 'What is our average deal this year?', 'dealroot' ), __( 'Why did we lose deals this year?', 'dealroot' ), ), 'fill' => false, ), array( 'title' => __( 'Lists and files', 'dealroot' ), 'icon' => 'dashicons-media-spreadsheet', 'examples' => array( __( 'Show the open enquiries of this week', 'dealroot' ), __( 'Show the deals won this month', 'dealroot' ), __( 'Export the customers of last month as CSV', 'dealroot' ), __( 'Make a PDF report for last month', 'dealroot' ), ), 'fill' => false, ), array( 'title' => __( 'Change a lead', 'dealroot' ), 'icon' => 'dashicons-edit', 'examples' => array( __( 'Move Meier to contacted', 'dealroot' ), __( 'Müller won, 1,500 euros', 'dealroot' ), __( 'Schmidt cancelled, too expensive', 'dealroot' ), ), 'fill' => true, ), array( 'title' => __( 'Reminders, notes and answers', 'dealroot' ), 'icon' => 'dashicons-clock', 'examples' => array( __( 'Remind me of Schmidt on Friday at 10', 'dealroot' ), __( 'Note for Meier: wants white roses only', 'dealroot' ), __( 'Answer Mrs Schmidt that I will call back at 2 pm', 'dealroot' ), ), 'fill' => true, ), array( 'title' => __( 'A new lead from a call', 'dealroot' ), 'icon' => 'dashicons-phone', 'examples' => array( __( 'Müller called about the roof, wants an estimate, about 2,000 euros', 'dealroot' ), __( 'Mrs Wagner called: wedding on 12 June, wants table flowers', 'dealroot' ), ), 'fill' => true, ), ); /** * Filters the groups of examples next to the questions about the leads. * * @param array[] $groups Each title, dashicon, examples and whether they only fill in the field. */ return (array) apply_filters( 'dealroot_ask_groups', $groups ); } /** * A question about the leads, in plain words. * * @param bool $own_screen Whether the panel fills a screen of its own, which has the title. */ public static function ask_panel( $own_screen = false ) { if ( ! Ai::enabled() ) { return; } $examples = array_keys( \Dealroot\Ai_Ask::examples() ); $history = \Dealroot\Ai_Ask::history(); ?>

>

>