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- Use

for main headings and

for subheadings.\r\n- Wrap paragraphs in

tags.\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; + } } }