diff --git a/inc/API.php b/inc/API.php index 4a96a8c..e82c6d7 100644 --- a/inc/API.php +++ b/inc/API.php @@ -244,7 +244,7 @@ public function update_settings( $request ) { 'default_message' => function ( $value ) { return is_string( $value ); }, - 'chat_model' => function ( $value ) { + 'chat_model' => function ( $value ) { return is_string( $value ); }, 'temperature' => function ( $value ) { @@ -723,6 +723,7 @@ function ( $row ) { 'thread_id' => $thread_id, 'query_run' => $query_run, 'record_id' => $record_id ? $record_id : null, + 'content' => $article_context, ) ); } diff --git a/inc/OpenAI.php b/inc/OpenAI.php index 6814d2e..827f1da 100644 --- a/inc/OpenAI.php +++ b/inc/OpenAI.php @@ -25,7 +25,7 @@ class OpenAI { * * @var string */ - private $prompt_version = '1.1.0'; + private $prompt_version = '1.2.0'; /** * Chat Model. @@ -64,6 +64,45 @@ public function __construct( $api_key = '' ) { } } + /** + * Get Assistant Properties. + * + * @return array + */ + public function get_properties() { + $props = array( + 'instructions' => "You are a Support Assistant tasked with providing precise, to-the-point answers based on the context provided for each query, as well as maintaining awareness of previous context for follow-up questions.\r\n\r\nCore Principles:\r\n\r\n1. Context and Question Analysis\r\n- Identify the context given in each message.\r\n- Determine the specific question to be answered based on the current context and previous interactions.\r\n\r\n2. Relevance Check\r\n- Assess if the current context or previous context contains information directly relevant to the question.\r\n- Proceed based on the following scenarios:\r\na) If current context addresses the question: Formulate a response using current context.\r\nb) If current context is empty but previous context is relevant: Use previous context to answer.\r\nc) If the input is a greeting: Respond appropriately.\r\nd) If neither current nor previous context addresses the question: Respond with an empty response and success: false.\r\n\r\n3. Response Formulation\r\n- Use information from the current context primarily. If current context is insufficient, refer to previous context for follow-up questions.\r\n- Include all relevant details, including any code snippets or links if present.\r\n- Avoid including unnecessary information.\r\n- Format the response in HTML using only these allowed tags: h2, h3, p, img, a, pre, strong, em.\r\n\r\n4. Context Reference\r\n- Do not explicitly mention or refer to the context in your answer.\r\n- Provide a straightforward response that directly answers the question.\r\n\r\n5. Response Structure\r\n- Always structure your response as a JSON object with 'response' and 'success' fields.\r\n- The 'response' field should contain the HTML-formatted answer.\r\n- The 'success' field should be a boolean indicating whether the question was successfully answered.\r\n\r\n6. Handling Follow-up Questions\r\n- Maintain awareness of previous context to answer follow-up questions.\r\n- If current context is empty but the question seems to be a follow-up, attempt to answer using previous context.\r\n\r\nExamples:\r\n\r\n1. Initial Question with Full Answer\r\nContext: The price of XYZ product is $99.99 USD.\r\nQuestion: How much does XYZ cost?\r\nResponse:\r\n{\r\n\"response\": \"
The price of XYZ product is $99.99 USD.
\",\r\n\"success\": true\r\n}\r\n\r\n2. Follow-up Question with Empty Current Context\r\nContext: [Empty]\r\nQuestion: What currency is that in?\r\nResponse:\r\n{\r\n\"response\": \"The price is in USD (United States Dollars).
\",\r\n\"success\": true\r\n}\r\n\r\n3. No Relevant Information in Current or Previous Context\r\nContext: [Empty]\r\nQuestion: Do you offer gift wrapping?\r\nResponse:\r\n{\r\n\"response\": \"\",\r\n\"success\": false\r\n}\r\n\r\n4. Greeting\r\nQuestion: Hello!\r\nResponse:\r\n{\r\n\"response\": \"Hello! How can I assist you today?
\",\r\n\"success\": true\r\n}\r\n\r\nError Handling:\r\nFor invalid inputs or unrecognized question formats, respond with:\r\n{\r\n\"response\": \"I apologize, but I couldn't understand your question. Could you please rephrase it?
\",\r\n\"success\": false\r\n}\r\n\r\nHTML Usage Guidelines:\r\n- Usetags.\r\n- Use
for code snippets or formatted text.\r\n- Apply for bold and for italic emphasis sparingly.\r\n- Include only if specific image information is provided in the context.\r\n- Use for links, ensuring they are relevant and from the provided context.\r\n\r\nRemember:\r\n- Prioritize using the current context for answers.\r\n- For follow-up questions with empty current context, refer to previous context if relevant.\r\n- If information isn't available in current or previous context, indicate this with an empty response and success: false.\r\n- Always strive to provide the most accurate and relevant information based on available context.", + 'model' => $this->chat_model, + ); + + if ( 'gpt-4o-mini' === $this->chat_model ) { + $props['response_format'] = array( + 'type' => 'json_schema', + 'json_schema' => array( + 'name' => 'chatbot_response', + 'strict' => false, + 'schema' => array( + 'type' => 'object', + 'properties' => array( + 'response' => array( + 'type' => 'string', + 'description' => 'The HTML-formatted response to the user\'s question.', + ), + 'success' => array( + 'type' => 'boolean', + 'description' => 'Indicates whether the question was successfully answered from the provided context.', + ), + ), + 'required' => array( 'success' ), + 'additionalProperties' => false, + ), + ), + ); + } + + return $props; + } + /** * Setup Assistant. * @@ -91,10 +130,11 @@ public function setup_assistant() { public function create_assistant() { $response = $this->request( 'assistants', - array( - 'instructions' => "Assistant Role & Concise Response Guidelines: As a Support Assistant, provide precise, to-the-point answers based exclusively on the previously provided context.\r\n\r\nSET OF PRINCIPLES TO FOLLOW:\r\n\r\n1. **Identify the Context and Question**:\r\n1.1. **START CONTEXT**: Identify the context provided in the message. **: END CONTEXT**\r\n1.2. **START QUESTION**: Identify the question that needs to be answered based on the context.. **: END QUESTION**\r\n\r\n2. **Check the Context for Relevance**:\r\n2.1. Determine if the context contains information directly relevant to the question.\r\n2.2. If the context addresses the user's question, proceed to the next step.\r\n2.3. If the question is a greeting, respond appropriately with the greeting.\r\n2.4. If the context does not address the user's question, respond with: `{\"response\": \"\", \"success\": false}`.\r\n\r\n3. **Formulate the Response**:\r\n3.1. If the context is sufficient, formulate a clear and concise response using only the information provided in the context.\r\n3.2. Ensure the response includes all important details covered in the context, but avoid any extraneous information.\r\n\r\n4. **Avoid Referring to the Context**:\r\n4.1. Do not refer to the context or state that the response is based on the context in your answer.\r\n4.2. Ensure the response is straightforward and directly answers the question.\r\n\r\n5. **Generate the JSON Response**:\r\n5.1. Structure the response according to the following JSON schema:\r\n\r\n\r\n{\r\n \"\$schema\": \"http:\/\/json-schema.org\/draft-07\/schema#\",\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"response\": {\r\n \"type\": \"string\",\r\n \"description\": \"Contains the response to the question. Do not include it if the answer wasn't available in the context.\"\r\n },\r\n \"success\": {\r\n \"type\": \"boolean\",\r\n \"description\": \"Indicates whether the question was successfully answered from provided context.\"\r\n }\r\n },\r\n \"required\": [\"success\"]\r\n}\r\n\r\nExample Usage:\r\n\r\nContext: [Provide context here]\r\nQuestion: [Provide question here]\r\n\r\nExpected Behavior:\r\n\r\n- If the question is fully covered by the context, provide a response using the provided JSON schema.\r\n- If the question is not fully covered by the context, respond with: {\"response\": \"\", \"success\": false}.\r\n\r\nExample Responses:\r\n\r\n- Context covers the question: {\"response\": \"Here is the information you requested.\", \"success\": true}\r\n- Context does not cover the question: {\"response\": \"\", \"success\": false}\r\n- Context does not cover the question but is a greeting: {\"response\": \"Hello, what can I help you with?.\", \"success\": true}", - 'name' => 'Chatbot by Hyve', - 'model' => $this->chat_model, + array_merge( + $this->get_properties(), + array( + 'name' => 'Chatbot by Hyve', + ) ) ); @@ -133,15 +173,13 @@ public function update_assistant() { } else { $response = $this->request( 'assistants/' . $this->assistant_id, - array( - 'instructions' => "Assistant Role & Concise Response Guidelines: As a Support Assistant, provide precise, to-the-point answers based exclusively on the previously provided context.\r\n\r\nSET OF PRINCIPLES TO FOLLOW:\r\n\r\n1. **Identify the Context and Question**:\r\n1.1. **START CONTEXT**: Identify the context provided in the message. **: END CONTEXT**\r\n1.2. **START QUESTION**: Identify the question that needs to be answered based on the context.. **: END QUESTION**\r\n\r\n2. **Check the Context for Relevance**:\r\n2.1. Determine if the context contains information directly relevant to the question.\r\n2.2. If the context addresses the user's question, proceed to the next step.\r\n2.3. If the question is a greeting, respond appropriately with the greeting.\r\n2.4. If the context does not address the user's question, respond with: `{\"response\": \"\", \"success\": false}`.\r\n\r\n3. **Formulate the Response**:\r\n3.1. If the context is sufficient, formulate a clear and concise response using only the information provided in the context.\r\n3.2. Ensure the response includes all important details covered in the context, but avoid any extraneous information.\r\n\r\n4. **Avoid Referring to the Context**:\r\n4.1. Do not refer to the context or state that the response is based on the context in your answer.\r\n4.2. Ensure the response is straightforward and directly answers the question.\r\n\r\n5. **Generate the JSON Response**:\r\n5.1. Structure the response according to the following JSON schema:\r\n\r\n\r\n{\r\n \"\$schema\": \"http:\/\/json-schema.org\/draft-07\/schema#\",\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"response\": {\r\n \"type\": \"string\",\r\n \"description\": \"Contains the response to the question. Do not include it if the answer wasn't available in the context.\"\r\n },\r\n \"success\": {\r\n \"type\": \"boolean\",\r\n \"description\": \"Indicates whether the question was successfully answered from provided context.\"\r\n }\r\n },\r\n \"required\": [\"success\"]\r\n}\r\n\r\nExample Usage:\r\n\r\nContext: [Provide context here]\r\nQuestion: [Provide question here]\r\n\r\nExpected Behavior:\r\n\r\n- If the question is fully covered by the context, provide a response using the provided JSON schema.\r\n- If the question is not fully covered by the context, respond with: {\"response\": \"\", \"success\": false}.\r\n\r\nExample Responses:\r\n\r\n- Context covers the question: {\"response\": \"Here is the information you requested.\", \"success\": true}\r\n- Context does not cover the question: {\"response\": \"\", \"success\": false}\r\n- Context does not cover the question but is a greeting: {\"response\": \"Hello, what can I help you with?.\", \"success\": true}", - ) + $this->get_properties() ); if ( is_wp_error( $response ) ) { return $response; } - + if ( ! isset( $response->id ) ) { return false; } diff --git a/src/backend/utils.js b/src/backend/utils.js index 22f222c..80e2792 100644 --- a/src/backend/utils.js +++ b/src/backend/utils.js @@ -55,8 +55,6 @@ export const tokenize = ( post, chunk = true ) => { content } = post; - content = content.replace( /<[^>]+>/g, '' ); - const tokens = tokenizer.encode( content ); const article = { diff --git a/src/frontend/App.js b/src/frontend/App.js index d0cf8f8..c9b30b2 100644 --- a/src/frontend/App.js +++ b/src/frontend/App.js @@ -65,7 +65,11 @@ class App { add( message, sender, id = null ) { const time = new Date(); - message = this.sanitize( message ); + if ( 'user' === sender ) { + message = this.sanitize( message ); + } + + message = this.addTargetBlack( message ); this.messages.push({ time, message, sender, id }); this.addMessage( time, message, sender, id ); @@ -89,6 +93,19 @@ class App { return tempDiv.innerHTML; } + addTargetBlack( message ) { + const tempDiv = document.createElement( 'div' ); + tempDiv.innerHTML = message; + + const links = tempDiv.querySelectorAll( 'a' ); + + links.forEach( link => { + link.target = '_blank'; + }); + + return tempDiv.innerHTML; + } + setThreadID( threadID ) { this.threadID = threadID; } @@ -204,7 +221,7 @@ class App { const date = `${ String( datetime.getDate() ).padStart( 2, 0 ) }/${ String( datetime.getMonth() + 1 ).padStart( 2, 0 ) }/${ datetime.getFullYear() } ${ String( datetime.getHours() ).padStart( 2, 0 ) }:${ String( datetime.getMinutes() ).padStart( 2, 0 ) } ${ 12 <= datetime.getHours() ? 'PM' : 'AM' }`; - let messageHTML = `${message}
`; + let messageHTML = `${message}`; if ( null === id ) { messageHTML += ``; diff --git a/src/frontend/style.scss b/src/frontend/style.scss index 4b5c34d..693dd55 100644 --- a/src/frontend/style.scss +++ b/src/frontend/style.scss @@ -239,9 +239,8 @@ display: flex; flex-direction: column; - p { + div { font-size: 13px; - display: flex; padding: 10px; border-radius: 5px; width: 100%; @@ -249,6 +248,19 @@ margin: 0; } + p { + font-size: 13px; + } + + h1, h2, h3, h4, h5, h6 { + font-size: 13px; + margin-bottom: 12px; + } + + ol, ul { + padding-left: 12px; + } + time { font-size: 10px; padding: 4px; @@ -274,7 +286,7 @@ color: #000000; } - p { + div { background-color: var( --user_background, #1155cc ); justify-content: flex-end; } @@ -295,9 +307,53 @@ color: #ffffff; } - p { + div { background-color: var( --assistant_background, #ecf1fb ); justify-content: flex-start; + + p { + margin: 0px; + padding: 0px; + } + + pre { + background-color: #282c34; + color: #abb2bf; + padding: 1em; + border-radius: 0; + font-family: monospace; + line-height: 1.5; + overflow-x: auto; + white-space: pre; + word-spacing: normal; + margin: 20px -10px; + + * { + color: #abb2bf; + word-break: normal; + word-wrap: normal; + } + + ::selection { + background-color: #3e4451; + } + + ::before { + content: attr(data-content); + } + + ::before:has(+ :matches(function, const, return, let, var)) { + color: #c678dd; + } + } + + code { + background: #d0effb; + border-radius: 5px; + border: none; + padding: 0 3px; + color: #333; + } } }