| <?php
$apiKey = "your-openai-api-key-here"; # set your openai key here
// version: 1.0
// filename: example.phpopenaichat.php
// author: https://www.phpclasses.org/browse/author/144301.html
// description: An application to hold conversations using the OpenAI API to access the GPT-3.5-Turbo model and the GPT-4 model.
// license: BSD License
// TODO: DONE. Conversation loading/saving
// TODO: DONE. Edit previous message sections by clicking their div in the window to change their content
// TODO: When contracting messages array to fit into maxtokens, start throwing away everything after first agent and first prompt.
// TODO: A oneshot section for a single message that is not part of the conversation.
// TODO: DONE. save/load agent
/*
    Description:
        An application to hold conversations using the OpenAI API to access the GPT-3.5-Turbo model and the GPT-4 model.
        Very versatile and lets you edit the conversation's past to change the future responses.
        Let's you save/load/delete conversations and agents using JSON flatfiles.
        
        Programmed using PHP 8. Uses jQuery CDN for jQuery. PHP Curl required to connect to API with class.phpopenaichat.php.
        I found ChatGPT to be lacking, so I made my own. 
        Messages in conversation only show an excerpt. Click on them to see the full message.
*/
/*
    Features:
        - uses class class.phpopenaichat.php to drive the application's API request.
        - Persistent conversation using $_SESSION
        - Persistent controls that dynamically update on the server side whenever they are changed client side
        - Using jQuery to dynamically update the page without reloading it
        - Ability to save/load/delete conversations in a JSON file in the _saved directory (create in this program's root, and set as read/writeable
        - Ability to edit previous conversation messages by clicking on them in the window (Both agent and user messages can be edited)
        - Ability to save/load/delete agent into a JSON file in the _saved_agents directory (create in this program's root, and set as read/writeable
        - Minimal CSS styling for better code clarity
        - Ability to change the model, temperature, frequency penalty, presence penalty, and max tokens, previous conversation, and agent between prompts
        - frequency penalty, presence penalty let the AI know to not repeat itself or to not repeat the user's input
        - temperature is how random the AI's response is. 0.0 is the most predictable, 1.0+ is the most random
        - agents help the model align to a specific style of conversation. (ie. a doctor, a lawyer, a child, a teenager, a parent, etc.)
        - multiuser - Can run multiple instances at same time, but just use different browsers or a private window so that each instance has its own session.
        - GPT 4 is in beta at the moment, so you have to request access.
        - API access costs money, so be aware of that. (gpt 3.5 turbo is dirt cheap at the moment)
        - This is not secured for production use. It is just a proof of concept, and made for closed/private access from trusted individuals.
        - Using a command processor on the return from the server let's the server dynamically redefine the controls on the client side.
        - server has a copy of the clients data, so client's side is more of an indicator than a director.
        - API can take a few seconds to return a response, so be patient when it seems nothing is working after sending a prompt.
        devnotes: june 2023
        - added to example:
        - added support for gpt-3.5-turbo-16k (16k token model of gpt-3.5-turbo)
        - added support for gpt-4-32k (32k token model of gpt-4)
*/
    // start our session for persistent parts of the application
    ob_start(); session_start();
    // destroy session
    // session_destroy(); exit;
    // setting some defaults for the application
    if(!isset($_SESSION['messages']))
        $_SESSION['messages'] = []; // current conversation
    if(!isset($_SESSION['agent']))
        $_SESSION['agent'] = "You are a friendly and helpful assistant.";
    
    if(!isset($_SESSION['model']))
        $_SESSION['model'] = "gpt-3.5-turbo"; // default model
    if(!isset($_SESSION['temperature']))
        $_SESSION['temperature'] = 1.0; // default temperature
    if(!isset($_SESSION['freq_penalty']))
        $_SESSION['freq_penalty'] = 0.0; // default frequency penalty
    if(!isset($_SESSION['pres_penalty']))
        $_SESSION['pres_penalty'] = 0.0; // default presence penalty
    if(!isset($_SESSION['max_tokens']))
        $_SESSION['max_tokens'] = 4090; // default max tokens
    # ---
    require_once('class.phpopenaichat.php');
    // create new instance of PHPOpenAIChat
    $openAIChat = new PHPOpenAIChat($apiKey);
    // unused?... for now
    function send_and_append($prompt, $messages)
    {
        global $openAIChat;
        $response   = $openAIChat->sendMessage($messages);
        $text       = $openAIChat->get_response_text($response);
        $messages   = $openAIChat->append_response_to_messages($messages, $text);
        return $messages;
    } // end send_and_append()
    function html_messages($messages)
    {
        if(empty($messages))
            return '';
        $html = '
            <style>
                .messages {
                    padding-bottom:200px;
                }
    
                .message_content textarea {
                width: 98%;
                /* height: 100%; */
                height:auto;
                border: none;
                background-color: transparent;
            }
            </style>
        ';
        $row_count = 0;
        $even_odd = "odd";
        foreach($messages as $message)
        {
            $the_content = htmlentities($message['content']);
            $html .= '<div class="message '.$even_odd.'" row_count="'.$row_count.'" >';
            $html .= '<div class="message_role">'.$message['role'].'</div>';
            $html .= '<div class="message_content"><textarea class="autoExpand" name="message_content_textarea" row_count="'.
                $row_count.'" onchange="btn_update_message_content(this);">'.
                $the_content.'</textarea></div>';
            $html .= '</div>';
            $row_count++;
            $even_odd = ($even_odd == "even") ? "odd" : "even";
        }
        return $html;
    } // end html_messages()
    function change_message_content($messages, $index, $content)
    {
        // will let us click and edit a message div and have ajax send back to server to change conversation history
        $messages[$index]['content'] = $content;
        return $messages;
    } // end change_message_content()
    function get_conversation_titles()
    {
        $titles = [];
        $files = glob('_saved/*.json');
        foreach($files as $file)
        {
            $json = file_get_contents($file);
            $json = json_decode($json, true);
            // substr out the _saved/ part of the filename
            $just_file = substr($file, strlen('_saved/')  );
            $titles[] = [
                'title' => $json['title'],
                'file' => $just_file
            ];
        }
        return $titles;
    } // end get_conversation_titles()
    function html_conversation_combobox()
    {
        // return the html for a dropdown combobox of all the saved conversations
        $titles = get_conversation_titles();
        $html = '<select id="conversation_combobox" name="conversation_combobox" >';
        foreach($titles as $title)
        {
            $html .= '<option value="'.$title['file'].'">'.$title['title'].'</option>';
        }
        $html .= '</select>';
        return $html;
    }
    function get_agents_titles()
    {
        $titles = [];
        $files = glob('_saved_agents/*.json');
        foreach($files as $file)
        {
            $json = file_get_contents($file);
            $json = json_decode($json, true);
            // substr out the _agents/ part of the filename
            $just_file = substr($file, strlen('_saved_agents/')  );
            $titles[] = [
                'title' => $json['title'],
                'file' => $just_file
            ];
        }
        return $titles;
    } // end get_agents_titles()
    function html_agents_combobox()
    {
        // return the html for a dropdown combobox of all the saved agents
        $titles = get_agents_titles();
        $html = '<select id="agents_combobox" name="agents_combobox" >';
        foreach($titles as $title)
        {
            $html .= '<option value="'.$title['file'].'">'.$title['title'].'</option>';
        }
        $html .= '</select>';
        return $html;
    } // end html_agents_combobox()
    # ---
    # gets ajax request and then returns json data and quits.
    if(!empty($_GET['ajax']))
    {        
        switch($_GET['ajax'])
        {
            case 'delete_conversation':
                $file = $_POST['flds']['file'];
                $file = trim($file);
                if(empty($file)) {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "delete_conversation",
                        "msg" 		=> "file empty"
                    );
                    break; // error: no file specified
                }
                $file = '_saved/'.$file;
                // if file doesn't exist error out
                if(!file_exists($file)) {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "delete_conversation",
                        "msg" 		=> "file does not exist"
                    );
                    break; // error: file does not exist
                }
                unlink($file);
                $return_arr[] = array(
                    "command" 	=> 'alert',
                    "process" 	=> "delete_conversation",
                    "msg" 		=> "file deleted"
                );
                $return_arr[] = array(
                    "command" 	=> 'html',
                    "selector" 	=> '#conversation_combobox',
                    "msg" 		=> html_conversation_combobox()
                );
                break;
            case 'load_conversation':
                $file = $_POST['flds']['file'];
                $file = trim($file);
                if(empty($file)) {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "load_conversation",
                        "msg" 		=> "file empty"
                    );
                    break; // error: no file specified
                }
                $file = '_saved/'.$file;
                // if file doesn't exist error out
                if(!file_exists($file)) {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "load_conversation",
                        "msg" 		=> "file does not exist"
                    );
                    break; // error: file does not exist
                }
                $json = file_get_contents($file);
                $json = json_decode($json, true);
                $messages = $json['messages'];
                $_SESSION['messages'] = $messages;
                $messages_html = html_messages($messages);
                $return_arr[] = array(
                    "command" 	=> 'html',
                    "process" 	=> "load_conversation",
                    "selector" 	=> '.output',
                    "msg" 		=> $messages_html
                );
                // update textareas
                $return_arr[] = array(
                    "command" 	=> 'resize_textareas',
                    "process" 	=> "load_conversation"
                );
                break;
            case 'save_conversation':
                $conversation_title = $_POST['flds']['title'];
                $messages = $_SESSION['messages'];
                // now create a folder named '_saved' and save the conversation as a json file in that folder
                // file will be a timestamp_hash.json
                // json will be format:
                        // "title": "conversation title",
                        // "messages": {$_SESSION['messages']}
                // create folder if it doesn't exist
                if(!file_exists('_saved'))
                    @mkdir('_saved');
                // if dir doesn't exist... abort with alert error
                if(!file_exists('_saved'))
                {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "save_conversation",
                        "msg" 		=> "could not create _saved folder"
                    );
                    break; // error: could not create _saved folder
                }
                // create a unique filename
                $filename = time().'_'.md5(time()).'.json';
                $filepath = '_saved/'.$filename;
                // save the file...
                $json_data = array(
                    "title"     => $conversation_title,
                    "messages"  => $messages
                );
                $json_data = json_encode($json_data);
                // store it to file
                $result = file_put_contents($filepath, $json_data);
                // create json data
                $json_data = array(
                    "title"     => $conversation_title,
                    "messages"  => $messages
                );
                
                $return_arr[] = array(
                    "command" 	=> 'alert',
                    "process" 	=> "save_conversation",
                    "msg" 		=> "saved conversation"
                );
                $return_arr[] = array(
                    "command" 	=> 'val',
                    'selector' 	=> 'input[name="save-message_title"]',
                    'msg' 		=> ''
                );
                $return_arr[] = array(
                    "command" 	=> 'html',
                    "selector" 	=> '#conversation_combobox',
                    "msg" 		=> html_conversation_combobox()
                );
                    
                break; // case 'save_conversation'
            
            
            case 'change_message_content':
                $index = $_POST['flds']['row_count'];
                $content = $_POST['flds']['message_content'];
                $messages = $_SESSION['messages'];
                $messages = change_message_content($messages, $index, $content);
                $_SESSION['messages'] = $messages;
                $return_arr[] = array(
                    "command" 	=> 'alert',
                    "process" 	=> "change_message_content",
                    "msg" 		=> "changed message content",
                    "index" 	=> $index,
                    "content" 	=> $content
                );
                break; // case 'change_message_content'
            case 'update_conversation':
                $messages = $_SESSION['messages'];
                $html_messages = html_messages($messages);
                $return_arr[] = array(
                    "command" 	=> 'html',
                    'selector' 	=> '.output',
                    'msg' 		=> $html_messages
                );
                break; // case 'update_conversation'
            case 'delete_agent':
                $file = $_POST['flds']['file'];
                $file = trim($file);
                if(empty($file)) {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "delete_agent",
                        "msg" 		=> "file empty"
                    );
                    break; // error: no file specified
                }
                $file = '_saved_agents/'.$file;
                // if file doesn't exist error out
                if(!file_exists($file)) {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "delete_agent",
                        "msg" 		=> "file does not exist"
                    );
                    break; // error: file does not exist
                }
                unlink($file);
                $return_arr[] = array(
                    "command" 	=> 'alert',
                    "process" 	=> "delete_agent",
                    "msg" 		=> "file deleted"
                );
                $return_arr[] = array(
                    "command" 	=> 'html',
                    "selector" 	=> '#agents_combobox',
                    "msg" 		=> html_agents_combobox()
                );
                break;
            case 'save_agent':
                $agent_name = $_POST['flds']['title'];
                $agent = $_SESSION['agent'];
                // create folder if it doesn't exist
                if(!file_exists('_saved_agents'))
                    @mkdir('_saved_agents');
                
                // if dir doesn't exist... abort with alert error
                if(!file_exists('_saved_agents'))
                {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "save_agent",
                        "msg" 		=> "could not create _saved_agents folder"
                    );
                    break; // error: could not create _saved_agents folder
                }
                // create a unique filename 
                $filename = time().'_'.md5(time()).'.json';
                $filepath = '_saved_agents/'.$filename;
                // save the file...
                $json_data = array(
                    "title"     => $agent_name,
                    "agent"     => $agent
                );
                $json_data = json_encode($json_data);
                // store it to file
                $result = file_put_contents($filepath, $json_data);
                $return_arr[] = array(
                    "command" 	=> 'alert',
                    "process" 	=> "save_agent",
                    "msg" 		=> "saved agent"
                );
                $return_arr[] = array(
                    "command" 	=> 'val',
                    'selector' 	=> 'input[name="save-agent_title"]',
                    'msg' 		=> ''
                );
                $return_arr[] = array(
                    "command" 	=> 'html',
                    "selector" 	=> '#agents_combobox',
                    "msg" 		=> html_agents_combobox()
                );
            
                break; // case 'save_assistant'
                
            case 'load_agent':
                $file = $_POST['flds']['file'];
                $file = trim($file);
                if(empty($file)) {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "load_agent",
                        "msg" 		=> "file empty"
                    );
                    break; // error: no file specified
                }
                $file = '_saved_agents/'.$file;
                // if file doesn't exist error out
                if(!file_exists($file)) {
                    $return_arr[] = array(
                        "command" 	=> 'alert',
                        "process" 	=> "load_agent",
                        "msg" 		=> "file does not exist"
                    );
                    break; // error: file does not exist
                }
                $json = file_get_contents($file);
                $json = json_decode($json, true);
                $agent = $json['agent'];
                $_SESSION['agent'] = $agent;
                
                $return_arr[] = array(
                    "command" 	=> 'val',
                    "process" 	=> "load_agent",
                    "selector" 	=> '#text_agent',
                    "msg" 		=> $agent
                );
                break; // case 'load_agent'
            case 'one-shot':
                // function one_shot($api_key, $prompt, $agent='You are a helpful assistant.', $temperature=1.0, $max_tokens=4000, $model="gpt-3.5-turbo")
                // returns text
                /*
            "prompt": $('textarea[name="prompt"]').val(),
            "agent": $('textarea[name="agent"]').val(),
            "temperature": $('input[name="temperature"]').val(),
            "max_tokens": $('input[name="max_tokens"]').val(),
                */
                global $apiKey;
                $prompt = $_POST['flds']['prompt'];
                $agent = $_POST['flds']['agent'];
                $temperature = $_POST['flds']['temperature'];
                $max_tokens = 4000;
                $model = $_POST['flds']['model'];
                $api_key = $apiKey;
                // trim and clean $prompt
                $prompt = trim($prompt);
                // if prompt empty return error
                $text = one_shot($api_key, $prompt, $agent, $temperature, $max_tokens, $model);
                $return_arr[] = array(
                    "command" 	=> 'val',
                    "process" 	=> "one-shot",
                    "selector" 	=> 'textarea[name="response"]',
                    "msg" 		=> $text
                );
                $return_arr[] = array(
                    'command' => 'enable_input',
                    'selector' => '.one_shot .the-button'
                );
                break; // case 'one-shot'
// ---------------------------------------------------------------------
            case 'prompt':
                    $prompt = $_POST['flds']['prompt'];
                    // trim and clean $prompt
                    $prompt = trim($prompt);
                    // if prompt empty return error
                    if(empty($prompt))
                    {
                        $return_arr[] = array(
                            "command" 	=> 'alert',
                            "process" 	=> "prompt",
                            "msg" 		=> "prompt is empty"
                        );
                    } else {
                        $messages = $_SESSION['messages'];
                        // first set agent
                        $messages = $openAIChat->set_agent($messages, $_SESSION['agent']);
                        $openAIChat->model          = $_SESSION['model'];
                        $openAIChat->temperature    = (float) round($_SESSION['temperature'],1);
                        $openAIChat->freq_penalty   = (float) round($_SESSION['freq_penalty'],1); 
                        $openAIChat->pres_penalty   = (float) round($_SESSION['pres_penalty'],1); 
                        $openAIChat->set_max_tokens( 4090 );
                        // print_r($prompt);
                        // add prompt to messages conversation
                        $messages   = $openAIChat->add_prompt_to_messages($messages, $prompt);
                        // print("\r\n\r\n");
                        // print_r($messages);
                        $response   = $openAIChat->sendMessage($messages);
                        $text       = $openAIChat->get_response_text($response);
                        // print_r($response);
                        // print("\r\n\r\n-----------\r\n\r\n");
                        // print($text);
                        // if text empty return error
                        if(empty($text))
                        {
                            $return_arr[] = array(
                                "command" 	=> 'alert',
                                "process" 	=> "prompt",
                                "msg" 		=> "api error:returned nothing",
                                "model"     => $_SESSION['model'],
                                "response" 	=> $response
                            );
                            break;
                        }
                        
                        // append response to messages conversation
                        $messages = $openAIChat->append_response_to_messages($messages, $text);
                        // save messages to session
                        $_SESSION['messages'] = $messages;
                        $return_arr[] = array(
                            "command" 	=> 'success',
                            "process" 	=> "prompt",
                            "prompt" 	=> $prompt,
                            "agent" 	=> $_SESSION['agent'],
                            "model" 	=> $_SESSION['model'],
                            "temperature" 	=> $_SESSION['temperature'],
                            "freq_penalty" 	=> $_SESSION['freq_penalty'],
                            "pres_penalty" 	=> $_SESSION['pres_penalty'],
                            "max_tokens" 	=> $_SESSION['max_tokens'],
                            "text" 		=> $text,
                            "response" 	=> $response
                        );
                        $return_arr[] = array(
                            "command" 	=> 'update_conversation'
                        );
                        $return_arr[] = array(
                            "command" 	=> 'val',
                            'selector' 	=> 'textarea[name="text_prompt"]',
                            'msg' 		=> ''
                        );
                        $return_arr[] = array(
                            'command' => 'html',
                            'selector' => '.output',
                            'msg' => html_messages($messages)
                        );
                        $return_arr[] = array(
                            'command' => 'enable_input',
                            'selector' => '#send-button'
                        );
                        $return_arr[] = array(
                            'command' => 'resize_textareas'
                        );
        
                    }
                break; // case 'prompt'
// ---------------------------------------------------------------------
            
            case 'change_agent':
                $agent = $_POST['flds']['agent'];
                $_SESSION['agent'] = $agent;
                $return_arr[] = array(
                    "command" 	=> 'success',
                    "process" 	=> "change_agent"
                );
                $return_arr[] = array(
                    "command" 	=> 'response_text',
                    "agent" 	=> $agent
                );
                break; // case 'change_agent'
            
            case 'change_model':
                $model = $_POST['flds']['model'];
                $_SESSION['model'] = $model;
                $return_arr[] = array(
                    "command" 	=> 'success',
                    "process" 	=> "change_model",
                    "model" 	=> $model
                );
                break; // case 'change_model'
            
            case 'change_temperature':
                $temperature = $_POST['flds']['temperature'];
                $_SESSION['temperature'] = $temperature;
                $return_arr[] = array(
                    "command" 	=> 'success',
                    "process" 	=> "change_temperature",
                    "temperature" 	=> $temperature
                );
                print("TEMP CHANGED");
                break; // case 'change_temperature'
            case 'change_freq_penalty':
                $freq_penalty = $_POST['flds']['freq_penalty'];
                $_SESSION['freq_penalty'] = $freq_penalty;
                $return_arr[] = array(
                    "command" 	=> 'success',
                    "process" 	=> "change_freq_penalty",
                    "freq_penalty" 	=> $freq_penalty
                );
                break; // case 'change_freq_penalty'
            case 'change_pres_penalty':
                $pres_penalty = $_POST['flds']['pres_penalty'];
                $_SESSION['pres_penalty'] = $pres_penalty;
                $return_arr[] = array(
                    "command" 	=> 'success',
                    "process" 	=> "change_pres_penalty",
                    "pres_penalty" 	=> $pres_penalty
                );
                break; // case 'change_pres_penalty'
            case 'change_max_tokens':
                $max_tokens = $_POST['flds']['max_tokens'];
                $_SESSION['max_tokens'] = $max_tokens;
                $return_arr[] = array(
                    "command" 	=> 'success',
                    "process" 	=> "change_max_tokens",
                    "max_tokens" 	=> $max_tokens
                );
                break; // case 'change_max_tokens'
            case 'reset_messages':
                $_SESSION['messages'] = [];
                $return_arr[] = array(
                    "command" 	=> 'success',
                    "process" 	=> "reset_messages"
                );
                $return_arr[] = array(
                    "command" => 'html',
                    'selector' => '.output',
                    'msg' => html_messages($_SESSION['messages'])
                );
                
                break; // case 'reset_messages'
        } // end switch($_GET['ajax'])
        
        if(!empty($return_arr) && is_array($return_arr))
        die(json_encode($return_arr));
        die();
    } // end if(!empty($_GET['ajax']))
    ?>
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
        <script>
            // BEGIN --> JAVASCRIPT COMMAND PROCESSOR //
            function do_cmd_post(url, send_data)
            {
                $.post( url, { flds: send_data /* in php will appear as $_POST['flds']  */ }, 
                    function( return_data ) { 
                    do_cmd_process(return_data); 
                    }, "json" ); // punt any returned data to processor
                    
            }
            // ---
            function do_cmd_process(data) // handle data coming back from ajax
            {
                if (data instanceof Array) {
                    data.forEach(function(entry) {
                        console.log(entry.command);
                        
                        //console.log(entry.message);
                        // handle returned commands //
                        switch(entry.command)
                        {
                            // generic commands //
                            case 'alert':
                                alert(entry.msg);
                                break;
                            case 'log':
                                console.log(entry.msg);
                                break;
                            case 'append':
                                $(entry.selector).append(entry.msg);
                                break;
                            case 'prepend':
                                $(entry.selector).prepend(entry.msg);
                                break;
                            case 'html':
                                $(entry.selector).html(entry.msg);
                                break;
                            case 'val':
                                $(entry.selector).val(entry.msg);
                                break;
                            case 'focus':
                                $(entry.selector).focus();
                                break;
                            case 'blur':
                                $(entry.selector).blur();
                                break;
                            case 'clear':
                                $(entry.selector).val('');
                                break;
                            case 'js':
                                eval(entry.msg);
                                break;
                            case 'resize_textarea_to_fit_contents':
                                $(entry.selector).height(0);
                                $(entry.selector).height($(entry.selector)[0].scrollHeight);
                                break;
                            case 'disable_input':
                                $(entry.selector).prop('disabled', true);
                                break;
                            case 'enable_input':
                                $(entry.selector).prop('disabled', false);
                                break;
                            case 'resize_textareas':
                                $(".message_content textarea").each(function(){
                                    $(this).css("height", ($(this).prop("scrollHeight")) + "px");
                                });
                                break;
                        } // end : switch (entry.command)
                    }); // end : data.forEach(function(entry)
                } // end : if (data instanceof Array)
            } // end : function do_cmd_process(data)
            // END --> JAVASCRIPT COMMAND PROCESSOR //
            
        </script>
    </head>
    <body>
    <br /><br />
    <?php
    
    ?>
    <div class='output' id="output" style='width: 100%; overflow: scroll; border: 1px solid #000;'><?php echo html_messages($_SESSION['messages']);  ?></div>
    <div class='row'>
        <input type='text' name='save-message_title' id='save-message_title' value='' placeholder='Save Conversation Title' />
        <input type='button' value='Save Conversation' onclick='btn_save_conversation();' />
        <?php  echo html_conversation_combobox(); ?>
        <input type='button' value='Load Conversation' onclick='btn_load_conversation();' />
        <input type='button' value='Delete Conversation' onclick='btn_delete_conversation();' />
        <input type='button' value='Reset Conversation' onclick='btn_reset_messages();' />
    </div>
    <br />
    <br />
    <textarea name="text_agent" id='text_agent' 
        onchange="btn_change_agent();" 
        style='width: 100%; height: 100px; overflow: scroll; border: 1px solid #000;'
        ><?php echo $_SESSION['agent']; ?></textarea>
    <div class='row'>
        <input type='text' name='save-agent_title' id='save-agent_title' value='' placeholder='Save Agent Title' />
        <input type='button' value='Save Agent' onclick='btn_save_agent();' />
        <?php  echo html_agents_combobox(); ?>
        <input type='button' value='Load Agent' onclick='btn_load_agent();' />
        <input type='button' value='Update Agent' onclick='btn_change_agent();'  />
        <input type='button' value='Delete Agent' onclick='btn_delete_agent();'  />
    </div>
    
    <br /><br />
    <textarea name="text_prompt" style='width: 100%; height: 100px; overflow: scroll; border: 1px solid #000;'></textarea>
    <input type='button' id='send-button' value='Send Prompt' onclick='btn_send_prompt();' />
    <br /><br />
    <!-- a combo box to select between gpt-3.5-turbo and gpt-4 -->
    <select id="model" name="model" onchange="btn_change_model();">
    <option value="gpt-3.5-turbo" <?php if($_SESSION['model']=="gpt-3.5-turbo") echo "SELECTED" ?> >gpt-3.5-turbo</option>
    <option value="gpt-3.5-turbo-16k" <?php if($_SESSION['model']=="gpt-3.5-turbo-16k") echo "SELECTED" ?> >gpt-3.5-turbo-16k</option>
        <option value="gpt-4" <?php if($_SESSION['model']=="gpt-4") echo "SELECTED" ?>>gpt-4</option>
        <option value="gpt-4-32k" <?php if($_SESSION['model']=="gpt-4-32k") echo "SELECTED" ?>>gpt-4-32k</option>
    </select>
    <script>
        function btn_delete_agent()
        {
            // confirm delete //
            if(!confirm('Are you sure you want to delete this agent?'))
                return;
            var send_data   = {
                "file": $('select[name="agents_combobox"]').val()
            };
            do_cmd_post('example.phpopenaichat.php?ajax=delete_agent', send_data);
        }
        function btn_save_agent()
        {
            // data already on server so just send a title.
            var send_data   = {
                "title": $('input[name="save-agent_title"]').val()
            };
            do_cmd_post('example.phpopenaichat.php?ajax=save_agent', send_data);
        }
        function btn_load_agent()
        {
            var send_data   = {
                "file": $('select[name="agents_combobox"]').val()
            };
            do_cmd_post('example.phpopenaichat.php?ajax=load_agent', send_data);
        }
        function btn_delete_conversation()
        {
            // confirm delete
            if(!confirm('Are you sure you want to delete this conversation?'))
                return;
            var send_data   = {
                "file": $('select[name="conversation_combobox"]').val()
            };
            do_cmd_post('example.phpopenaichat.php?ajax=delete_conversation', send_data);
        }
        function btn_load_conversation()
        {
            var send_data   = {
                "file": $('select[name="conversation_combobox"]').val()
            };
            do_cmd_post('example.phpopenaichat.php?ajax=load_conversation', send_data);
        }
        function btn_save_conversation()
        {
            // data already on server so just send a title.
            // if title empty, alert and abort.
            if($('input[name="save-message_title"]').val()=='')
            {
                alert('Please enter a title for this conversation.');
                return;
            }
            var send_data   = {
                "title": $('input[name="save-message_title"]').val()
            };
            do_cmd_post('example.phpopenaichat.php?ajax=save_conversation', send_data);
        }
        function btn_send_prompt()
        {
            // disable #send-button
            $('#send-button').prop('disabled', true);
            // confirm
            if(!confirm('Are you sure you want to send this prompt?'))
            {
                $('#send-button').prop('disabled', false);
                return;
            }
            var send_data   = {
                "prompt": $('textarea[name="text_prompt"]').val(),
            };
            do_cmd_post('example.phpopenaichat.php?ajax=prompt', send_data);
        }
        function btn_change_agent()
        {
            // confirm changes 
            if(!confirm('Are you sure you want to change the agent on the server? (server must have an agent sent for it to process an agent)'))
                return;
            var send_data   = {
                "agent": $('textarea[name="text_agent"]').val(),
            };
            do_cmd_post('example.phpopenaichat.php?ajax=change_agent', send_data);
        }
        function btn_change_model()
        {
            var send_data   = {
                "model": $('select[name="model"]').val(),
            };
            do_cmd_post('example.phpopenaichat.php?ajax=change_model', send_data);
        }
        function btn_change_temp()
        {
            var send_data   = {
                "temperature": $('input[name="temperature"]').val(),
            };
            do_cmd_post('example.phpopenaichat.php?ajax=change_temperature', send_data);
        }
        function btn_change_freq_penalty()
        {
            var send_data   = {
                "freq_penalty": $('input[name="freq_penalty"]').val(),
            };
            do_cmd_post('example.phpopenaichat.php?ajax=change_freq_penalty', send_data);
        }
        function btn_change_pres_penalty()
        {
            var send_data   = {
                "pres_penalty": $('input[name="pres_penalty"]').val(),
            };
            do_cmd_post('example.phpopenaichat.php?ajax=change_pres_penalty', send_data);
        }
        function btn_reset_messages()
        {
            var send_data   = {
                "reset_messages": 1,
            };
            do_cmd_post('example.phpopenaichat.php?ajax=reset_messages', send_data);
        }
        function btn_max_tokens()
        {
            var send_data   = {
                "max_tokens": $('input[name="max_tokens"]').val(),
            };
            do_cmd_post('example.phpopenaichat.php?ajax=change_max_tokens', send_data);
        }
        function btn_edit_in_place()
        {
            /*
                $html .= '<div class="message" row_count="'+$row_count+'" >';
                $html .= '<div class="message_role">'.$message['role'].'</div>';
                $html .= '<div class="message_content" onclick="btn_edit_in_place();">'.$message['content'].'</div>';
                $html .= '</div>';
            */
            /* swap out the div with a textarea */
            var row_count = 1;
            var message_content = $('div.message_content[row_count="'+row_count+'"]').html();
            $('div.message_content[row_count="'+row_count+'"]').html('<textarea name="message_content" row_count="'+row_count+'" style="width: 100%; height: 100px; overflow: scroll; border: 1px solid #000;">'+message_content+'</textarea>');
    
        }
       
        function btn_update_message_content(that)
        {
            the_row = $(that).attr('row_count');
            the_msg = $(that).val();
            // var row_count = $('textarea[name="message_content"]').attr('row_count');
            // var message_content = $('textarea[name="message_content_textarea"]').val();
            // alert(the_msg);
            // alert(the_row);
            var send_data   = {
                "row_count": the_row,
                "message_content": the_msg,
            };
            // get user confirmation to continue
            if(confirm("Are you sure you want to update the message content?"))
            {
                do_cmd_post('example.phpopenaichat.php?ajax=change_message_content', send_data);
            } else {
                alert("reload page to restore original message content.");
            }
        }
        
    </script>
    <br /><br />
    <!-- a slider to select temperature -->
    <label for="temperature">Temperature</label>
    <input type="range" id="temperature" onchange="btn_change_temp();" name="temperature" min="0.0" max="2.0" step="0.1" value="<?php echo $_SESSION["temperature"] ?>">
    <div id="temperature_value"></div>
    <!-- a slider to select frequency penalty -->
    <label for="freq_penalty">Frequency Penalty</label>
    <input type="range" id="freq_penalty" onchange="btn_change_freq_penalty();" name="freq_penalty" min="0.0" max="1.0" step="0.1" value="<?php echo $_SESSION["freq_penalty"] ?>">
    <div id="freq_penalty_value"></div>
    <!-- a slider to select presence penalty -->
    <label for="pres_penalty">Presence Penalty</label>
    <input type="range" id="pres_penalty" onchange="btn_change_pres_penalty();" name="pres_penalty" min="0.0" max="1.0" step="0.1" value="<?php echo $_SESSION["pres_penalty"] ?>">
    <div id="pres_penalty_value"></div>
    <!-- a text input to select max tokens -->
    <label for="max_tokens">Max Tokens</label>
    <input type="number" id="max_tokens" onchange="btn_max_tokens();" name="max_tokens" min="1" max="100" value="<?php echo $_SESSION["max_tokens"] ?>">
    <br /><br />
    <!-- reset messages button -->
    <input type='button' value='Reset Messages' onclick='btn_reset_messages();' />
    <br /><br />
    <!-- jquery to add a div under the sliders to show the current value of the sliders. -->
    <script>
        $(document).ready(function(){
            $('#temperature_value').html($('#temperature').val());
            $('#freq_penalty_value').html($('#freq_penalty').val());
            $('#pres_penalty_value').html($('#pres_penalty').val());
        });
    </script>
    <!-- jquery to update the divs when the sliders are moved -->
    <script>
        $(document).ready(function(){
            $('#temperature').on('input', function() {
                $('#temperature_value').html($('#temperature').val());
            });
            $('#freq_penalty').on('input', function() {
                $('#freq_penalty_value').html($('#freq_penalty').val());
            });
            $('#pres_penalty').on('input', function() {
                $('#pres_penalty_value').html($('#pres_penalty').val());
            });
        });
    </script>
<pre>
    <?php 
        // print_r($_SESSION['messages']);
    ?>
    </pre>
<script>
    // make tab character act like a normal tab character in textareas
    
    $(document).delegate('textarea', 'keydown', function(e) {
        var keyCode = e.keyCode || e.which;
        if (keyCode == 9) {
            e.preventDefault();
            var start = $(this).get(0).selectionStart;
            var end = $(this).get(0).selectionEnd;
            // set textarea value to: text before caret + tab + text after caret
            $(this).val($(this).val().substring(0, start)
                        + "\t"
                        + $(this).val().substring(end));
            // put caret at right position again
            $(this).get(0).selectionStart =
            $(this).get(0).selectionEnd = start + 1;
        }
    });
    
</script>
<script>
            // javascript to handle expanding textarea to fit content height whenever focus is on it and user is typing
            $(document).ready(function(){
                // $(".message_content textarea").css("height", "auto");
                // set each textarea on load to the size of its contents in the .messages area
                $(".message_content textarea").each(function(){
                    $(this).css("height", ($(this).prop("scrollHeight")) + "px");
                });
                // handle dynamic loaded divs
                $(".message_content textarea").on("focus", function(){
                    // $(this).css("height", "auto");
                    $(this).css("height", ($(this).prop("scrollHeight")) + "px");
                });
                
                $(".message_content textarea").on("blur", function(){
                    // $(this).css("height", "auto");
                    $(this).css("height", ($(this).prop("scrollHeight")) + "px");
                });
                
                $(".message_content textarea").on("change", function(){
                    // $(this).css("height", "auto");
                    $(this).css("height", ($(this).prop("scrollHeight")) + "px");
                });
                
                // $(".message_content textarea").on("keyup", function(){
                //     $(this).css("height", "auto");
                //     // $(this).css("height", ($(this).prop("scrollHeight")) + "px");
                // });
                
            });
</script>
<style>
    .messages {
    }
    .message {
        border-bottom: 3px solid red;
    }
    .message.even {
        background-color: #eee;
    }
    .message_content {
        padding:3vw;
    }
    </style>
<!-- one shot section -->
<hr />
<?php 
    function one_shot($api_key, $prompt, $agent='You are a helpful assistant.', $temperature=1.0, $max_tokens=4000, $model="gpt-3.5-turbo")
    {
        $temperature    = (float) round($temperature,1);
        $max_tokens     = (int) $max_tokens;
        // if $prompt empty return '' else get response and return the text...
        $messages = [];
        $AIChat = new PHPOpenAIChat($api_key);
        $AIChat->model = $model;
        $AIChat->temperature = $temperature;
        $AIChat->set_max_tokens($max_tokens);
        $AIChat->set_agent($messages, $agent);
        $messages = $AIChat->add_prompt_to_messages($messages, $prompt);
        if (empty($prompt))
            return '';
        else
        {
            $response   = $AIChat->sendMessage($messages);
            $text       = $AIChat->get_response_text($response);
            // print_r($response);
            return $text;
        }
    } // end one_shot()
?>
<style>
    .one_shot {
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        grid-gap: 1vw;
    }
    .one_shot .col {
        padding: 1vw;
    }
    .one_shot textarea {
        width: 100%;
        height: 10vw;
    }
    .one_shot input {
        width: 100%;
    }
    </style>
<div class='title'>One Shot Section - Enter a prompt and get a single one-off response</div>
<div class='one_shot'>
    <div class='col'><textarea class='prompt' name='prompt' id='prompt' placeholder='prompt'></textarea></div>
    <div class='col'><textarea class='agent' name='agent' id='agent' placeholder='agent'></textarea></div>
    <div class='col'><textarea class='response' name='response' id='response' placeholder='response'></textarea></div>
    
    <!-- model combo box -->
    <!-- a combo box to select between gpt-3.5-turbo and gpt-4 -->
    <select id="model" name="model" onchange="btn_change_model();">
        <option value="gpt-3.5-turbo" <?php if($_SESSION['model']=="gpt-3.5-turbo") echo "SELECTED" ?> >gpt-3.5-turbo</option>
        <option value="gpt-4" <?php if($_SESSION['model']=="gpt-4") echo "SELECTED" ?>>gpt-4</option>
    </select>
    <div class='col'><input type='text' class='temperature' name='temperature' id='temperature' placeholder='temperature' value='1.0' /></div>  
    
    <div class='col'><input type='button' class='the-button' value='One Shot' onclick='btn_one_shot();' /></div>
</div><!-- end one shot section -->
<script>
    function btn_one_shot()
    {
        var send_data   = {
            "prompt": $('textarea[name="prompt"]').val(),
            "agent": $('textarea[name="agent"]').val(),
            "temperature": $('input[name="temperature"]').val(),
            "max_tokens": $('input[name="max_tokens"]').val(),
            "model": $('select[name="model"]').val(),
        };
        // disable #send-button
        $('.one_shot .the-button').prop('disabled', true);
        do_cmd_post('example.phpopenaichat.php?ajax=one-shot', send_data);
    }
</script>
    
    </body>
    </html>
    
 |