From 1c3ec06f29bb682ef0c3d67259a0f1cfe7811955 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Wed, 31 Jul 2024 17:54:28 +0530 Subject: [PATCH 01/10] dev: update rephrase tasks to enum --- apiserver/plane/ee/views/app/ai/rephrase.py | 31 +++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/apiserver/plane/ee/views/app/ai/rephrase.py b/apiserver/plane/ee/views/app/ai/rephrase.py index 180fb41aa6..f5d8c23709 100644 --- a/apiserver/plane/ee/views/app/ai/rephrase.py +++ b/apiserver/plane/ee/views/app/ai/rephrase.py @@ -1,5 +1,6 @@ # Python imports import os +from enum import Enum # Third party imports from openai import OpenAI @@ -12,6 +13,20 @@ from plane.ee.views.base import BaseAPIView from plane.ee.permissions import WorkspaceEntityPermission +class Task(Enum): + PARAPHRASE = "PARAPHRASE" + SIMPLIFY = "SIMPLIFY" + ELABORATE = "ELABORATE" + SUMMARIZE = "SUMMARIZE" + GET_TITLE = "GET_TITLE" + TONE = "TONE" + + +class Tone(Enum): + CASUAL = "CASUAL" + FORMAL = "FORMAL" + + class RephraseGrammarEndpoint(BaseAPIView): permission_classes = [ WorkspaceEntityPermission, @@ -19,7 +34,7 @@ class RephraseGrammarEndpoint(BaseAPIView): # Function to get system prompt based on task def get_system_prompt(task, tone=None): - if task == "paraphrase": + if task == Task.PARAPHRASE: return """ Correct the grammar of the following text, strictly following these instructions: 1. Ensure the new text is grammatically correct. @@ -28,7 +43,7 @@ class RephraseGrammarEndpoint(BaseAPIView): 4. Do not provide any unrelated data apart from the grammatically correct original text. """ - elif task == "simplify": + elif task == Task.SIMPLIFY: return """ Simplify the following text, strictly adhering to these guidelines: 1. Make the text more concise without losing its core meaning. @@ -39,7 +54,7 @@ class RephraseGrammarEndpoint(BaseAPIView): 6. Do not add any new information not present in the original text. """ - elif task == "elaborate": + elif task == Task.ELABORATE: return """ Elaborate on the following text, carefully following these instructions: 1. Add relevant details, examples, or explanations to enrich the content. @@ -50,7 +65,7 @@ class RephraseGrammarEndpoint(BaseAPIView): 6. Do not contradict any information in the original text. """ - elif task == "summarize": + elif task == Task.SUMMARIZE: return """ Summarize the following text, strictly adhering to these guidelines: 1. Condense the text into a highly concise summary that is approximately 10-15 percent of the length of the input text. @@ -63,7 +78,7 @@ class RephraseGrammarEndpoint(BaseAPIView): 8. If the input text is very short (less than 100 words), aim for a summary of 1-2 sentences. """ - elif task == "get_title": + elif task == Task.GET_TITLE: return """ Generate an appropriate title for the following text, strictly following these instructions: 1. Create a concise and engaging title that captures the main theme or purpose of the content. @@ -74,7 +89,7 @@ class RephraseGrammarEndpoint(BaseAPIView): 6. If the text is clearly part of a larger work or series, reflect that in the title if appropriate. """ - elif task == "tone": + elif task == Task.TONE: base_instructions = """ Rewrite the following text in the specified tone, strictly adhering to these guidelines: @@ -90,7 +105,7 @@ class RephraseGrammarEndpoint(BaseAPIView): 7. Adjust sentence structure and vocabulary to fit the desired tone without changing the core message. """ - if tone == "casual": + if tone == Tone.CASUAL: return ( base_instructions + """ @@ -104,7 +119,7 @@ class RephraseGrammarEndpoint(BaseAPIView): 7. Aim for a Flesch-Kincaid grade level of 6-8. """ ) - elif tone == "formal": + elif tone == Tone.FORMAL: return ( base_instructions + """ From 946b08063ecba39082395b4df1afde0566936c91 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 7 Aug 2024 17:46:51 +0530 Subject: [PATCH 02/10] fix: rephrase endpoint --- apiserver/plane/ee/views/app/ai/rephrase.py | 93 +++++++++++++-------- 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/apiserver/plane/ee/views/app/ai/rephrase.py b/apiserver/plane/ee/views/app/ai/rephrase.py index f5d8c23709..0633cd961a 100644 --- a/apiserver/plane/ee/views/app/ai/rephrase.py +++ b/apiserver/plane/ee/views/app/ai/rephrase.py @@ -33,41 +33,52 @@ class RephraseGrammarEndpoint(BaseAPIView): ] # Function to get system prompt based on task - def get_system_prompt(task, tone=None): - if task == Task.PARAPHRASE: - return """ - Correct the grammar of the following text, strictly following these instructions: + def get_system_prompt(self, task, tone=None): + if task == Task.PARAPHRASE.value: + return ( + True, + """ + Correct the grammar of the following text and return the output in same structure as input but just changing the text by strictly following these instructions: 1. Ensure the new text is grammatically correct. 2. Maintain the original meaning of the text. 3. Keep the same person in the text. First person remains first person, etc. If no person is mentioned, keep it the same as in the input text, whether third person or neutral. 4. Do not provide any unrelated data apart from the grammatically correct original text. - """ + """, + ) - elif task == Task.SIMPLIFY: - return """ - Simplify the following text, strictly adhering to these guidelines: + elif task == Task.SIMPLIFY.value: + return ( + True, + """ + Simplify the following text and return the output in same structure as input but just changing the text by strictly adhering to these guidelines: 1. Make the text more concise without losing its core meaning. 2. Remove unnecessary words and phrases. 3. Break down complex sentences into simpler ones. 4. Use simpler vocabulary where appropriate, without changing the overall tone. 5. Ensure the simplified text is still grammatically correct. 6. Do not add any new information not present in the original text. - """ + """, + ) - elif task == Task.ELABORATE: - return """ - Elaborate on the following text, carefully following these instructions: + elif task == Task.ELABORATE.value: + return ( + True, + """ + Elaborate on the following text and return the output in same structure as input but just changing the text by carefully following these instructions: 1. Add relevant details, examples, or explanations to enrich the content. 2. Expand on the main ideas while maintaining the original meaning and tone. 3. Provide context where needed to enhance understanding. 4. Ensure that your elaborations flow naturally with the existing text. 5. Keep the overall structure and key points of the original text intact. 6. Do not contradict any information in the original text. - """ + """, + ) - elif task == Task.SUMMARIZE: - return """ - Summarize the following text, strictly adhering to these guidelines: + elif task == Task.SUMMARIZE.value: + return ( + True, + """ + Summarize the following text and return the output in same structure as input but just changing the text by strictly adhering to these guidelines: 1. Condense the text into a highly concise summary that is approximately 10-15 percent of the length of the input text. 2. Capture only the most essential information and main ideas. 3. Use clear, straightforward language. @@ -76,22 +87,26 @@ class RephraseGrammarEndpoint(BaseAPIView): 6. Do not introduce any new information not present in the original text. 7. Ensure the summary provides a quick, easily digestible overview of the text's content. 8. If the input text is very short (less than 100 words), aim for a summary of 1-2 sentences. - """ + """, + ) - elif task == Task.GET_TITLE: - return """ - Generate an appropriate title for the following text, strictly following these instructions: + elif task == Task.GET_TITLE.value: + return ( + True, + """ + Generate an appropriate title for the following text and return the output in same structure as input but just changing the text by strictly following these instructions: 1. Create a concise and engaging title that captures the main theme or purpose of the content. 2. Ensure the title is relevant and accurately represents the text's subject matter. 3. Make the title attention-grabbing while maintaining accuracy. 4. Keep the title brief, ideally no more than 6-8 words. 5. Do not include any information in the title that is not present in or implied by the text. 6. If the text is clearly part of a larger work or series, reflect that in the title if appropriate. - """ + """, + ) - elif task == Task.TONE: + elif task == Task.TONE.value: base_instructions = """ - Rewrite the following text in the specified tone, strictly adhering to these guidelines: + Rewrite the following text in the specified tone and return the output in same structure as input but just changing the text by strictly adhering to these guidelines: 1. Maintain the original meaning and key information of the text. 2. Ensure the rewritten text is grammatically correct and coherent. @@ -105,8 +120,8 @@ class RephraseGrammarEndpoint(BaseAPIView): 7. Adjust sentence structure and vocabulary to fit the desired tone without changing the core message. """ - if tone == Tone.CASUAL: - return ( + if tone == Tone.CASUAL.value: + return True, ( base_instructions + """ For a casual tone: @@ -119,8 +134,8 @@ class RephraseGrammarEndpoint(BaseAPIView): 7. Aim for a Flesch-Kincaid grade level of 6-8. """ ) - elif tone == Tone.FORMAL: - return ( + elif tone == Tone.FORMAL.value: + return True, ( base_instructions + """ For a formal tone: @@ -136,16 +151,14 @@ class RephraseGrammarEndpoint(BaseAPIView): """ ) else: - return Response( - {"error": "Invalid tone. Must be 'formal' or 'casual'."}, - status=status.HTTP_400_BAD_REQUEST, - ) + return False, { + "error": "Invalid tone. Must be 'formal' or 'casual'." + } else: - return Response( - {"error": "Invalid task. Please provide a correct task name."}, - status=status.HTTP_400_BAD_REQUEST, - ) + return False, { + "error": "Invalid task. Please provide a correct task name." + } def post(self, request, slug): # Get the task and text input @@ -153,7 +166,7 @@ class RephraseGrammarEndpoint(BaseAPIView): text_input = request.data.get("text_input", "") # tone may be formal or casual - tone = request.data.get("tone") if task == "tone" else None + tone = request.data.get("tone") if task == Task.TONE else None # Check the text input if not text_input: @@ -191,13 +204,19 @@ class RephraseGrammarEndpoint(BaseAPIView): api_key=OPENAI_API_KEY, ) + processed, response = self.get_system_prompt(task, tone) + + # If error + if not processed: + return Response(response, status=status.HTTP_400_BAD_REQUEST) + # Create the completion completion = client.chat.completions.create( model=GPT_ENGINE, messages=[ { "role": "system", - "content": self.get_system_prompt(task, tone), + "content": response, }, *message_list, ], From a6b7f744e29951385f44f468f76628a3bda9ea52 Mon Sep 17 00:00:00 2001 From: Akhil Vamshi Konam Date: Wed, 7 Aug 2024 18:25:32 +0530 Subject: [PATCH 03/10] tone slider --- apiserver/plane/ee/views/app/ai/rephrase.py | 494 +++++++++++++++++--- 1 file changed, 442 insertions(+), 52 deletions(-) diff --git a/apiserver/plane/ee/views/app/ai/rephrase.py b/apiserver/plane/ee/views/app/ai/rephrase.py index 0633cd961a..6966d31183 100644 --- a/apiserver/plane/ee/views/app/ai/rephrase.py +++ b/apiserver/plane/ee/views/app/ai/rephrase.py @@ -22,18 +22,13 @@ class Task(Enum): TONE = "TONE" -class Tone(Enum): - CASUAL = "CASUAL" - FORMAL = "FORMAL" - - class RephraseGrammarEndpoint(BaseAPIView): permission_classes = [ WorkspaceEntityPermission, ] # Function to get system prompt based on task - def get_system_prompt(self, task, tone=None): + def get_system_prompt(self, task, casual_score=5, formal_score=5): if task == Task.PARAPHRASE.value: return ( True, @@ -106,54 +101,133 @@ class RephraseGrammarEndpoint(BaseAPIView): elif task == Task.TONE.value: base_instructions = """ - Rewrite the following text in the specified tone and return the output in same structure as input but just changing the text by strictly adhering to these guidelines: + Rewrite the following text according to the specified readability score and return the output in the same structure as the input, changing only the text. Strictly adhere to these guidelines: 1. Maintain the original meaning and key information of the text. 2. Ensure the rewritten text is grammatically correct and coherent. - 3. Adapt the following elements to fit the desired tone: - - Paragraph structure and length - - Use of examples or anecdotes - - Level of detail and explanation - 4. Consider the context and purpose of the text when adjusting the tone. - 5. Maintain consistency in tone throughout the entire text. - 6. Do not add or remove significant information from the original text. - 7. Adjust sentence structure and vocabulary to fit the desired tone without changing the core message. + 3. Consider the context and purpose of the text when adjusting the tone. + 4. Maintain consistency in style throughout the entire text. + 5. Do not add or remove significant information from the original text. + 6. Adjust the structure and length as needed to match the tone. """ + tone_specific_instructions = { + (10, 0): """ + Extremely Casual + 1. Use the simplest, most conversational language possible. + 2. Write as if speaking to a close friend or family member. + 3. Use lots of contractions, slang, and colloquialisms. + 4. Keep sentences very short and simple. + 5. Use active voice exclusively. + 6. Explain complex ideas using everyday analogies and examples. + 7. Address the reader directly and use a very friendly, personal tone. + 8. Use emotive language and exclamations where appropriate. + """, + (9, 1): """ + Very Casual + 1. Use simple, conversational language. + 2. Include contractions and some common colloquialisms. + 3. Keep sentences short and straightforward. + 4. Use mostly active voice. + 5. Explain any technical terms in simple language. + 6. Address the reader directly in a friendly manner. + 7. Use a personal, engaging tone throughout. + """, + (8, 2): """ + Casual + 1. Use everyday language with some more complex words. + 2. Mix short and medium-length sentences. + 3. Use contractions frequently. + 4. Incorporate some idiomatic expressions. + 5. Maintain a friendly, approachable tone. + 6. Use personal pronouns and address the reader directly. + 7. Simplify complex ideas but retain some field-specific terms with explanations. + """, + (7, 3): """ + Somewhat Casual + 1. Balance everyday language with more sophisticated vocabulary. + 2. Use a mix of simple and complex sentences. + 3. Include contractions in most cases. + 4. Maintain a friendly tone while introducing some more formal elements. + 5. Use mostly active voice, but include some passive constructions. + 6. Explain technical terms, but use them where appropriate. + 7. Address the reader directly, but less frequently than in more casual styles. + """, + (6, 4): """ + Neutral Leaning Casual + 1. Use a balanced mix of casual and formal language. + 2. Construct sentences of varying complexity. + 3. Use contractions in some cases, but not always. + 4. Maintain a friendly yet professional tone. + 5. Include technical terms where necessary, with brief explanations. + 6. Use a mix of active and passive voice. + 7. Address the reader directly occasionally, but not consistently. + """, + (5, 5): """ + Neutral + 1. Strike a perfect balance between casual and formal language. + 2. Use a varied vocabulary that includes both common and more sophisticated words. + 3. Construct sentences of mixed complexity. + 4. Use contractions sparingly. + 5. Maintain a tone that is neither overly friendly nor too formal. + 6. Include technical terms where appropriate, with explanations when necessary. + 7. Balance active and passive voice equally. + 8. Address the reader directly only when it serves a clear purpose. + """, + (4, 6): """ + Neutral Leaning Formal + 1. Use more sophisticated vocabulary, but keep some casual elements. + 2. Construct mostly complex sentences with some simpler ones for balance. + 3. Use contractions occasionally, but prefer full forms. + 4. Maintain a professional tone with hints of friendliness. + 5. Incorporate field-specific terminology with brief explanations. + 6. Use more passive voice constructions. + 7. Address the reader directly only when necessary for clarity or emphasis. + """, + (3, 7): """ + Somewhat Formal + 1. Use more advanced vocabulary and complex sentence structures. + 2. Minimize use of contractions. + 3. Maintain a professional and objective tone. + 4. Incorporate field-specific terminology freely, explaining only the most complex terms. + 5. Use passive voice more frequently. + 6. Avoid directly addressing the reader unless absolutely necessary. + 7. Present information in a more detached, analytical manner. + """, + (2, 8): """ + Formal + 1. Use sophisticated vocabulary and complex sentence structures. + 2. Avoid contractions entirely. + 3. Maintain a highly professional and objective tone. + 4. Use field-specific terminology without explanation, unless extremely specialized. + 5. Prefer passive voice in most cases. + 6. Never address the reader directly. + 7. Present information in a scholarly, analytical manner. + """, + (1, 9): """ + Very Formal + 1. Use advanced, academic vocabulary and intricate sentence structures. + 2. Construct complex, multi-clause sentences. + 3. Maintain a strictly professional, detached tone. + 4. Use extensive field-specific terminology and concepts. + 5. Employ passive voice predominantly. + 6. Present information in a highly analytical and abstract manner. + 7. Use impersonal constructions throughout. + """, + (0, 10): """ + Extremely Formal + 1. Use the most advanced, scholarly vocabulary possible. + 2. Construct highly complex, multi-layered sentences. + 3. Maintain an extremely formal, impersonal tone throughout. + 4. Use the most specialized terminology and abstract concepts without explanation. + 5. Use passive voice and impersonal constructions almost exclusively. + 6. Present information in the most academic, theoretical manner possible. + 7. Avoid any hint of casualness or personal address. + """, + } - if tone == Tone.CASUAL.value: - return True, ( - base_instructions - + """ - For a casual tone: - 1. Use conversational and everyday language. - 2. Incorporate contractions, colloquialisms, and common idioms. - 3. Use active voice predominantly. - 4. Employ simpler sentence structures. - 5. Use more common vocabulary and explain technical terms in lay terms. - 6. Incorporate personal experiences and relatable scenarios where appropriate from the input. - 7. Aim for a Flesch-Kincaid grade level of 6-8. - """ - ) - elif tone == Tone.FORMAL.value: - return True, ( - base_instructions - + """ - For a formal tone: - 1. Use professional and academic language. - 2. Avoid contractions, colloquialisms, and idioms. - 3. Maintain a respectful, objective, and impersonal voice. - 4. Use passive voice where appropriate. - 5. Employ more complex sentence structures. - 6. Utilize precise vocabulary and technical terms when relevant. - 7. Focus on facts, data, and logical arguments. - 8. Maintain technical terms and provide context if necessary. - 9. Aim for a Flesch-Kincaid grade level of 11-12 or higher. - """ - ) - else: - return False, { - "error": "Invalid tone. Must be 'formal' or 'casual'." - } + return True, base_instructions + tone_specific_instructions.get( + (casual_score, formal_score) + ) else: return False, { @@ -165,8 +239,9 @@ class RephraseGrammarEndpoint(BaseAPIView): task = request.data.get("task", "grammar_check") text_input = request.data.get("text_input", "") - # tone may be formal or casual - tone = request.data.get("tone") if task == Task.TONE else None + # Get casual and formal scores for the tone task + casual_score = int(request.data.get("casual_score", 5)) + formal_score = int(request.data.get("formal_score", 5)) # Check the text input if not text_input: @@ -204,7 +279,322 @@ class RephraseGrammarEndpoint(BaseAPIView): api_key=OPENAI_API_KEY, ) - processed, response = self.get_system_prompt(task, tone) + processed, response = self.get_system_prompt( + task, casual_score, formal_score + ) + + # If error + if not processed: + return Response(response, status=status.HTTP_400_BAD_REQUEST) + + # Create the completion + completion = client.chat.completions.create( + model=GPT_ENGINE, + messages=[ + { + "role": "system", + "content": response, + }, + *message_list, + ], + temperature=0.1, + ) + + # Get the response + response = completion.choices[0].message.content.strip() + + return Response( + { + "response": response, + }, + status=status.HTTP_200_OK, + ) + + +# Python imports +import os +from enum import Enum + +# Third party imports +from openai import OpenAI +from rest_framework import status +from rest_framework.response import Response + +# Module imports +from plane.license.utils.instance_value import get_configuration_value +from plane.ee.views.base import BaseAPIView +from plane.ee.permissions import WorkspaceEntityPermission + + +class Task(Enum): + PARAPHRASE = "PARAPHRASE" + SIMPLIFY = "SIMPLIFY" + ELABORATE = "ELABORATE" + SUMMARIZE = "SUMMARIZE" + GET_TITLE = "GET_TITLE" + TONE = "TONE" + + +class RephraseGrammarEndpoint(BaseAPIView): + permission_classes = [ + WorkspaceEntityPermission, + ] + + # Function to get system prompt based on task + def get_system_prompt(self, task, casual_score=5, formal_score=5): + if task == Task.PARAPHRASE.value: + return ( + True, + """ + Correct the grammar of the following text and return the output in same structure as input but just changing the text by strictly following these instructions: + 1. Ensure the new text is grammatically correct. + 2. Maintain the original meaning of the text. + 3. Keep the same person in the text. First person remains first person, etc. If no person is mentioned, keep it the same as in the input text, whether third person or neutral. + 4. Do not provide any unrelated data apart from the grammatically correct original text. + """, + ) + + elif task == Task.SIMPLIFY.value: + return ( + True, + """ + Simplify the following text and return the output in same structure as input but just changing the text by strictly adhering to these guidelines: + 1. Make the text more concise without losing its core meaning. + 2. Remove unnecessary words and phrases. + 3. Break down complex sentences into simpler ones. + 4. Use simpler vocabulary where appropriate, without changing the overall tone. + 5. Ensure the simplified text is still grammatically correct. + 6. Do not add any new information not present in the original text. + """, + ) + + elif task == Task.ELABORATE.value: + return ( + True, + """ + Elaborate on the following text and return the output in same structure as input but just changing the text by carefully following these instructions: + 1. Add relevant details, examples, or explanations to enrich the content. + 2. Expand on the main ideas while maintaining the original meaning and tone. + 3. Provide context where needed to enhance understanding. + 4. Ensure that your elaborations flow naturally with the existing text. + 5. Keep the overall structure and key points of the original text intact. + 6. Do not contradict any information in the original text. + """, + ) + + elif task == Task.SUMMARIZE.value: + return ( + True, + """ + Summarize the following text and return the output in same structure as input but just changing the text by strictly adhering to these guidelines: + 1. Condense the text into a highly concise summary that is approximately 10-15 percent of the length of the input text. + 2. Capture only the most essential information and main ideas. + 3. Use clear, straightforward language. + 4. Avoid detailed examples or minor points. + 5. Maintain the original tone and perspective of the text. + 6. Do not introduce any new information not present in the original text. + 7. Ensure the summary provides a quick, easily digestible overview of the text's content. + 8. If the input text is very short (less than 100 words), aim for a summary of 1-2 sentences. + """, + ) + + elif task == Task.GET_TITLE.value: + return ( + True, + """ + Generate an appropriate title for the following text and return the output in same structure as input but just changing the text by strictly following these instructions: + 1. Create a concise and engaging title that captures the main theme or purpose of the content. + 2. Ensure the title is relevant and accurately represents the text's subject matter. + 3. Make the title attention-grabbing while maintaining accuracy. + 4. Keep the title brief, ideally no more than 6-8 words. + 5. Do not include any information in the title that is not present in or implied by the text. + 6. If the text is clearly part of a larger work or series, reflect that in the title if appropriate. + """, + ) + + elif task == Task.TONE.value: + base_instructions = """ + Rewrite the following text according to the specified readability score and return the output in the same structure as the input, changing only the text. Strictly adhere to these guidelines: + + 1. Maintain the original meaning and key information of the text. + 2. Ensure the rewritten text is grammatically correct and coherent. + 3. Consider the context and purpose of the text when adjusting the tone. + 4. Maintain consistency in style throughout the entire text. + 5. Do not add or remove significant information from the original text. + 6. Adjust the structure and length as needed to match the tone. + """ + tone_specific_instructions = { + (10, 0): """ + Extremely Casual + 1. Use the simplest, most conversational language possible. + 2. Write as if speaking to a close friend or family member. + 3. Use lots of contractions, slang, and colloquialisms. + 4. Keep sentences very short and simple. + 5. Use active voice exclusively. + 6. Explain complex ideas using everyday analogies and examples. + 7. Address the reader directly and use a very friendly, personal tone. + 8. Use emotive language and exclamations where appropriate. + """, + (9, 1): """ + Very Casual + 1. Use simple, conversational language. + 2. Include contractions and some common colloquialisms. + 3. Keep sentences short and straightforward. + 4. Use mostly active voice. + 5. Explain any technical terms in simple language. + 6. Address the reader directly in a friendly manner. + 7. Use a personal, engaging tone throughout. + """, + (8, 2): """ + Casual + 1. Use everyday language with some more complex words. + 2. Mix short and medium-length sentences. + 3. Use contractions frequently. + 4. Incorporate some idiomatic expressions. + 5. Maintain a friendly, approachable tone. + 6. Use personal pronouns and address the reader directly. + 7. Simplify complex ideas but retain some field-specific terms with explanations. + """, + (7, 3): """ + Somewhat Casual + 1. Balance everyday language with more sophisticated vocabulary. + 2. Use a mix of simple and complex sentences. + 3. Include contractions in most cases. + 4. Maintain a friendly tone while introducing some more formal elements. + 5. Use mostly active voice, but include some passive constructions. + 6. Explain technical terms, but use them where appropriate. + 7. Address the reader directly, but less frequently than in more casual styles. + """, + (6, 4): """ + Neutral Leaning Casual + 1. Use a balanced mix of casual and formal language. + 2. Construct sentences of varying complexity. + 3. Use contractions in some cases, but not always. + 4. Maintain a friendly yet professional tone. + 5. Include technical terms where necessary, with brief explanations. + 6. Use a mix of active and passive voice. + 7. Address the reader directly occasionally, but not consistently. + """, + (5, 5): """ + Neutral + 1. Strike a perfect balance between casual and formal language. + 2. Use a varied vocabulary that includes both common and more sophisticated words. + 3. Construct sentences of mixed complexity. + 4. Use contractions sparingly. + 5. Maintain a tone that is neither overly friendly nor too formal. + 6. Include technical terms where appropriate, with explanations when necessary. + 7. Balance active and passive voice equally. + 8. Address the reader directly only when it serves a clear purpose. + """, + (4, 6): """ + Neutral Leaning Formal + 1. Use more sophisticated vocabulary, but keep some casual elements. + 2. Construct mostly complex sentences with some simpler ones for balance. + 3. Use contractions occasionally, but prefer full forms. + 4. Maintain a professional tone with hints of friendliness. + 5. Incorporate field-specific terminology with brief explanations. + 6. Use more passive voice constructions. + 7. Address the reader directly only when necessary for clarity or emphasis. + """, + (3, 7): """ + Somewhat Formal + 1. Use more advanced vocabulary and complex sentence structures. + 2. Minimize use of contractions. + 3. Maintain a professional and objective tone. + 4. Incorporate field-specific terminology freely, explaining only the most complex terms. + 5. Use passive voice more frequently. + 6. Avoid directly addressing the reader unless absolutely necessary. + 7. Present information in a more detached, analytical manner. + """, + (2, 8): """ + Formal + 1. Use sophisticated vocabulary and complex sentence structures. + 2. Avoid contractions entirely. + 3. Maintain a highly professional and objective tone. + 4. Use field-specific terminology without explanation, unless extremely specialized. + 5. Prefer passive voice in most cases. + 6. Never address the reader directly. + 7. Present information in a scholarly, analytical manner. + """, + (1, 9): """ + Very Formal + 1. Use advanced, academic vocabulary and intricate sentence structures. + 2. Construct complex, multi-clause sentences. + 3. Maintain a strictly professional, detached tone. + 4. Use extensive field-specific terminology and concepts. + 5. Employ passive voice predominantly. + 6. Present information in a highly analytical and abstract manner. + 7. Use impersonal constructions throughout. + """, + (0, 10): """ + Extremely Formal + 1. Use the most advanced, scholarly vocabulary possible. + 2. Construct highly complex, multi-layered sentences. + 3. Maintain an extremely formal, impersonal tone throughout. + 4. Use the most specialized terminology and abstract concepts without explanation. + 5. Use passive voice and impersonal constructions almost exclusively. + 6. Present information in the most academic, theoretical manner possible. + 7. Avoid any hint of casualness or personal address. + """, + } + + return True, base_instructions + tone_specific_instructions.get( + (casual_score, formal_score) + ) + + else: + return False, { + "error": "Invalid task. Please provide a correct task name." + } + + def post(self, request, slug): + # Get the task and text input + task = request.data.get("task", "grammar_check") + text_input = request.data.get("text_input", "") + + # Get casual and formal scores for the tone task + casual_score = int(request.data.get("casual_score", 5)) + formal_score = int(request.data.get("formal_score", 5)) + + # Check the text input + if not text_input: + return Response( + {"error": "Text input is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Get the configuration value + OPENAI_API_KEY, GPT_ENGINE = get_configuration_value( + [ + { + "key": "OPENAI_API_KEY", + "default": os.environ.get("OPENAI_API_KEY", None), + }, + { + "key": "GPT_ENGINE", + "default": os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"), + }, + ] + ) + + # Check the keys + if not OPENAI_API_KEY or not GPT_ENGINE: + return Response( + {"error": "OpenAI API key and engine is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Create the message list + message_list = [{"role": "user", "content": text_input}] + + # Create the client + client = OpenAI( + api_key=OPENAI_API_KEY, + ) + + processed, response = self.get_system_prompt( + task, casual_score, formal_score + ) # If error if not processed: From cfe6df1b3d616bcca68a624f86da1f4e1baab8c7 Mon Sep 17 00:00:00 2001 From: Akhil Vamshi Konam Date: Wed, 7 Aug 2024 18:37:27 +0530 Subject: [PATCH 04/10] feat: rephrase tone slider --- apiserver/plane/ee/views/app/ai/rephrase.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apiserver/plane/ee/views/app/ai/rephrase.py b/apiserver/plane/ee/views/app/ai/rephrase.py index 6966d31183..9d311f71f5 100644 --- a/apiserver/plane/ee/views/app/ai/rephrase.py +++ b/apiserver/plane/ee/views/app/ai/rephrase.py @@ -556,6 +556,19 @@ class RephraseGrammarEndpoint(BaseAPIView): casual_score = int(request.data.get("casual_score", 5)) formal_score = int(request.data.get("formal_score", 5)) + # Check the scores + if ( + casual_score + formal_score != 10 + or casual_score < 0 + or formal_score < 0 + ): + return Response( + { + "error": "Invalid scores. casual_score and formal_score must sum to 10 and both must be non-negative." + }, + status=status.HTTP_400_BAD_REQUEST, + ) + # Check the text input if not text_input: return Response( From 0653d3098a31e4927a232ec5cac8c28d067440a6 Mon Sep 17 00:00:00 2001 From: Akhil Vamshi Konam Date: Thu, 8 Aug 2024 11:48:31 +0530 Subject: [PATCH 05/10] feat: rephrase tone slider --- apiserver/plane/ee/views/app/ai/rephrase.py | 313 -------------------- 1 file changed, 313 deletions(-) diff --git a/apiserver/plane/ee/views/app/ai/rephrase.py b/apiserver/plane/ee/views/app/ai/rephrase.py index 9d311f71f5..0b84119724 100644 --- a/apiserver/plane/ee/views/app/ai/rephrase.py +++ b/apiserver/plane/ee/views/app/ai/rephrase.py @@ -234,319 +234,6 @@ class RephraseGrammarEndpoint(BaseAPIView): "error": "Invalid task. Please provide a correct task name." } - def post(self, request, slug): - # Get the task and text input - task = request.data.get("task", "grammar_check") - text_input = request.data.get("text_input", "") - - # Get casual and formal scores for the tone task - casual_score = int(request.data.get("casual_score", 5)) - formal_score = int(request.data.get("formal_score", 5)) - - # Check the text input - if not text_input: - return Response( - {"error": "Text input is required"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - # Get the configuration value - OPENAI_API_KEY, GPT_ENGINE = get_configuration_value( - [ - { - "key": "OPENAI_API_KEY", - "default": os.environ.get("OPENAI_API_KEY", None), - }, - { - "key": "GPT_ENGINE", - "default": os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"), - }, - ] - ) - - # Check the keys - if not OPENAI_API_KEY or not GPT_ENGINE: - return Response( - {"error": "OpenAI API key and engine is required"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - # Create the message list - message_list = [{"role": "user", "content": text_input}] - - # Create the client - client = OpenAI( - api_key=OPENAI_API_KEY, - ) - - processed, response = self.get_system_prompt( - task, casual_score, formal_score - ) - - # If error - if not processed: - return Response(response, status=status.HTTP_400_BAD_REQUEST) - - # Create the completion - completion = client.chat.completions.create( - model=GPT_ENGINE, - messages=[ - { - "role": "system", - "content": response, - }, - *message_list, - ], - temperature=0.1, - ) - - # Get the response - response = completion.choices[0].message.content.strip() - - return Response( - { - "response": response, - }, - status=status.HTTP_200_OK, - ) - - -# Python imports -import os -from enum import Enum - -# Third party imports -from openai import OpenAI -from rest_framework import status -from rest_framework.response import Response - -# Module imports -from plane.license.utils.instance_value import get_configuration_value -from plane.ee.views.base import BaseAPIView -from plane.ee.permissions import WorkspaceEntityPermission - - -class Task(Enum): - PARAPHRASE = "PARAPHRASE" - SIMPLIFY = "SIMPLIFY" - ELABORATE = "ELABORATE" - SUMMARIZE = "SUMMARIZE" - GET_TITLE = "GET_TITLE" - TONE = "TONE" - - -class RephraseGrammarEndpoint(BaseAPIView): - permission_classes = [ - WorkspaceEntityPermission, - ] - - # Function to get system prompt based on task - def get_system_prompt(self, task, casual_score=5, formal_score=5): - if task == Task.PARAPHRASE.value: - return ( - True, - """ - Correct the grammar of the following text and return the output in same structure as input but just changing the text by strictly following these instructions: - 1. Ensure the new text is grammatically correct. - 2. Maintain the original meaning of the text. - 3. Keep the same person in the text. First person remains first person, etc. If no person is mentioned, keep it the same as in the input text, whether third person or neutral. - 4. Do not provide any unrelated data apart from the grammatically correct original text. - """, - ) - - elif task == Task.SIMPLIFY.value: - return ( - True, - """ - Simplify the following text and return the output in same structure as input but just changing the text by strictly adhering to these guidelines: - 1. Make the text more concise without losing its core meaning. - 2. Remove unnecessary words and phrases. - 3. Break down complex sentences into simpler ones. - 4. Use simpler vocabulary where appropriate, without changing the overall tone. - 5. Ensure the simplified text is still grammatically correct. - 6. Do not add any new information not present in the original text. - """, - ) - - elif task == Task.ELABORATE.value: - return ( - True, - """ - Elaborate on the following text and return the output in same structure as input but just changing the text by carefully following these instructions: - 1. Add relevant details, examples, or explanations to enrich the content. - 2. Expand on the main ideas while maintaining the original meaning and tone. - 3. Provide context where needed to enhance understanding. - 4. Ensure that your elaborations flow naturally with the existing text. - 5. Keep the overall structure and key points of the original text intact. - 6. Do not contradict any information in the original text. - """, - ) - - elif task == Task.SUMMARIZE.value: - return ( - True, - """ - Summarize the following text and return the output in same structure as input but just changing the text by strictly adhering to these guidelines: - 1. Condense the text into a highly concise summary that is approximately 10-15 percent of the length of the input text. - 2. Capture only the most essential information and main ideas. - 3. Use clear, straightforward language. - 4. Avoid detailed examples or minor points. - 5. Maintain the original tone and perspective of the text. - 6. Do not introduce any new information not present in the original text. - 7. Ensure the summary provides a quick, easily digestible overview of the text's content. - 8. If the input text is very short (less than 100 words), aim for a summary of 1-2 sentences. - """, - ) - - elif task == Task.GET_TITLE.value: - return ( - True, - """ - Generate an appropriate title for the following text and return the output in same structure as input but just changing the text by strictly following these instructions: - 1. Create a concise and engaging title that captures the main theme or purpose of the content. - 2. Ensure the title is relevant and accurately represents the text's subject matter. - 3. Make the title attention-grabbing while maintaining accuracy. - 4. Keep the title brief, ideally no more than 6-8 words. - 5. Do not include any information in the title that is not present in or implied by the text. - 6. If the text is clearly part of a larger work or series, reflect that in the title if appropriate. - """, - ) - - elif task == Task.TONE.value: - base_instructions = """ - Rewrite the following text according to the specified readability score and return the output in the same structure as the input, changing only the text. Strictly adhere to these guidelines: - - 1. Maintain the original meaning and key information of the text. - 2. Ensure the rewritten text is grammatically correct and coherent. - 3. Consider the context and purpose of the text when adjusting the tone. - 4. Maintain consistency in style throughout the entire text. - 5. Do not add or remove significant information from the original text. - 6. Adjust the structure and length as needed to match the tone. - """ - tone_specific_instructions = { - (10, 0): """ - Extremely Casual - 1. Use the simplest, most conversational language possible. - 2. Write as if speaking to a close friend or family member. - 3. Use lots of contractions, slang, and colloquialisms. - 4. Keep sentences very short and simple. - 5. Use active voice exclusively. - 6. Explain complex ideas using everyday analogies and examples. - 7. Address the reader directly and use a very friendly, personal tone. - 8. Use emotive language and exclamations where appropriate. - """, - (9, 1): """ - Very Casual - 1. Use simple, conversational language. - 2. Include contractions and some common colloquialisms. - 3. Keep sentences short and straightforward. - 4. Use mostly active voice. - 5. Explain any technical terms in simple language. - 6. Address the reader directly in a friendly manner. - 7. Use a personal, engaging tone throughout. - """, - (8, 2): """ - Casual - 1. Use everyday language with some more complex words. - 2. Mix short and medium-length sentences. - 3. Use contractions frequently. - 4. Incorporate some idiomatic expressions. - 5. Maintain a friendly, approachable tone. - 6. Use personal pronouns and address the reader directly. - 7. Simplify complex ideas but retain some field-specific terms with explanations. - """, - (7, 3): """ - Somewhat Casual - 1. Balance everyday language with more sophisticated vocabulary. - 2. Use a mix of simple and complex sentences. - 3. Include contractions in most cases. - 4. Maintain a friendly tone while introducing some more formal elements. - 5. Use mostly active voice, but include some passive constructions. - 6. Explain technical terms, but use them where appropriate. - 7. Address the reader directly, but less frequently than in more casual styles. - """, - (6, 4): """ - Neutral Leaning Casual - 1. Use a balanced mix of casual and formal language. - 2. Construct sentences of varying complexity. - 3. Use contractions in some cases, but not always. - 4. Maintain a friendly yet professional tone. - 5. Include technical terms where necessary, with brief explanations. - 6. Use a mix of active and passive voice. - 7. Address the reader directly occasionally, but not consistently. - """, - (5, 5): """ - Neutral - 1. Strike a perfect balance between casual and formal language. - 2. Use a varied vocabulary that includes both common and more sophisticated words. - 3. Construct sentences of mixed complexity. - 4. Use contractions sparingly. - 5. Maintain a tone that is neither overly friendly nor too formal. - 6. Include technical terms where appropriate, with explanations when necessary. - 7. Balance active and passive voice equally. - 8. Address the reader directly only when it serves a clear purpose. - """, - (4, 6): """ - Neutral Leaning Formal - 1. Use more sophisticated vocabulary, but keep some casual elements. - 2. Construct mostly complex sentences with some simpler ones for balance. - 3. Use contractions occasionally, but prefer full forms. - 4. Maintain a professional tone with hints of friendliness. - 5. Incorporate field-specific terminology with brief explanations. - 6. Use more passive voice constructions. - 7. Address the reader directly only when necessary for clarity or emphasis. - """, - (3, 7): """ - Somewhat Formal - 1. Use more advanced vocabulary and complex sentence structures. - 2. Minimize use of contractions. - 3. Maintain a professional and objective tone. - 4. Incorporate field-specific terminology freely, explaining only the most complex terms. - 5. Use passive voice more frequently. - 6. Avoid directly addressing the reader unless absolutely necessary. - 7. Present information in a more detached, analytical manner. - """, - (2, 8): """ - Formal - 1. Use sophisticated vocabulary and complex sentence structures. - 2. Avoid contractions entirely. - 3. Maintain a highly professional and objective tone. - 4. Use field-specific terminology without explanation, unless extremely specialized. - 5. Prefer passive voice in most cases. - 6. Never address the reader directly. - 7. Present information in a scholarly, analytical manner. - """, - (1, 9): """ - Very Formal - 1. Use advanced, academic vocabulary and intricate sentence structures. - 2. Construct complex, multi-clause sentences. - 3. Maintain a strictly professional, detached tone. - 4. Use extensive field-specific terminology and concepts. - 5. Employ passive voice predominantly. - 6. Present information in a highly analytical and abstract manner. - 7. Use impersonal constructions throughout. - """, - (0, 10): """ - Extremely Formal - 1. Use the most advanced, scholarly vocabulary possible. - 2. Construct highly complex, multi-layered sentences. - 3. Maintain an extremely formal, impersonal tone throughout. - 4. Use the most specialized terminology and abstract concepts without explanation. - 5. Use passive voice and impersonal constructions almost exclusively. - 6. Present information in the most academic, theoretical manner possible. - 7. Avoid any hint of casualness or personal address. - """, - } - - return True, base_instructions + tone_specific_instructions.get( - (casual_score, formal_score) - ) - - else: - return False, { - "error": "Invalid task. Please provide a correct task name." - } - def post(self, request, slug): # Get the task and text input task = request.data.get("task", "grammar_check") From 485d1da6cb35a0478f5530c8b6645b1f6df1d4f8 Mon Sep 17 00:00:00 2001 From: Akhil Vamshi Konam Date: Mon, 12 Aug 2024 18:58:39 +0530 Subject: [PATCH 06/10] Rephrase: Ask AI --- apiserver/plane/ee/views/app/ai/rephrase.py | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/apiserver/plane/ee/views/app/ai/rephrase.py b/apiserver/plane/ee/views/app/ai/rephrase.py index 0b84119724..c8286bbc06 100644 --- a/apiserver/plane/ee/views/app/ai/rephrase.py +++ b/apiserver/plane/ee/views/app/ai/rephrase.py @@ -20,6 +20,7 @@ class Task(Enum): SUMMARIZE = "SUMMARIZE" GET_TITLE = "GET_TITLE" TONE = "TONE" + ASK_AI = "ASK_AI" class RephraseGrammarEndpoint(BaseAPIView): @@ -229,6 +230,31 @@ class RephraseGrammarEndpoint(BaseAPIView): (casual_score, formal_score) ) + elif task == Task.ASK_AI.value: + return ( + True, + """ + You are an AI assistant designed to provide helpful and appropriate information. Respond to queries using simple HTML tags for structure (no , , , or tags). Follow these guidelines: + + 1. Provide accurate, concise responses relevant to the query. + 2. If uncertain, express your limitations and suggest further research. + 3. Provide output format in the same HTML format as input, if its given. + 4. Use appropriate HTML tags (

,

,

    ,
  • , , ) for formatting. + 5. For controversial topics, provide balanced, factual information without bias. + 6. Respect privacy and avoid sensationalism when addressing sensitive topics. + 7. If confronted with queries about illegal activities, hate speech, or harmful content: + - Do not provide information on how to perform illegal or harmful acts. + - Respond with a brief, clear statement about the inappropriateness or illegality of the request. + - Redirect the conversation to legal and ethical alternatives if applicable. + 8. Keep responses brief and to the point, avoiding unnecessary elaboration. + 9. Do not use concluding phrases like "In summary" or "In conclusion." + 10. Ensure your HTML is well-formed and properly nested. + 11. If the query cannot be answered or violates OpenAI's usage policy, respond appropriately without offering any alternative answers or additional text. + + Your goal is to provide helpful, appropriate, and concise responses while maintaining ethical standards. + """, + ) + else: return False, { "error": "Invalid task. Please provide a correct task name." From 420c38016955abebf1e1687e156acda1e1c897f0 Mon Sep 17 00:00:00 2001 From: Akhil Vamshi Konam Date: Fri, 16 Aug 2024 16:04:56 +0530 Subject: [PATCH 07/10] refactor askAI --- apiserver/plane/ee/views/app/ai/rephrase.py | 88 +++++++++++---------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/apiserver/plane/ee/views/app/ai/rephrase.py b/apiserver/plane/ee/views/app/ai/rephrase.py index c8286bbc06..79f37d0f1d 100644 --- a/apiserver/plane/ee/views/app/ai/rephrase.py +++ b/apiserver/plane/ee/views/app/ai/rephrase.py @@ -234,24 +234,19 @@ class RephraseGrammarEndpoint(BaseAPIView): return ( True, """ - You are an AI assistant designed to provide helpful and appropriate information. Respond to queries using simple HTML tags for structure (no , , , or tags). Follow these guidelines: + You are an advanced AI assistant designed to provide optimal responses by integrating given context with your broad knowledge base. Your primary objectives are: - 1. Provide accurate, concise responses relevant to the query. - 2. If uncertain, express your limitations and suggest further research. - 3. Provide output format in the same HTML format as input, if its given. - 4. Use appropriate HTML tags (

    ,

    ,

      ,
    • , , ) for formatting. - 5. For controversial topics, provide balanced, factual information without bias. - 6. Respect privacy and avoid sensationalism when addressing sensitive topics. - 7. If confronted with queries about illegal activities, hate speech, or harmful content: - - Do not provide information on how to perform illegal or harmful acts. - - Respond with a brief, clear statement about the inappropriateness or illegality of the request. - - Redirect the conversation to legal and ethical alternatives if applicable. - 8. Keep responses brief and to the point, avoiding unnecessary elaboration. - 9. Do not use concluding phrases like "In summary" or "In conclusion." - 10. Ensure your HTML is well-formed and properly nested. - 11. If the query cannot be answered or violates OpenAI's usage policy, respond appropriately without offering any alternative answers or additional text. + 1. Thoroughly analyze and understand the provided context, which may include context, specific questions, code snippets, or any relevant information. + 2. Treat the given context as a critical input, using it to inform and guide your response. + 3. Leverage your extensive knowledge to complement and enhance your understanding of the context and to provide comprehensive, accurate answers. + 4. Seamlessly blend insights from the given context with your general knowledge, ensuring a cohesive and informative response. + 5. Adapt your response style and depth based on the nature of the context and the question asked. + 6. When dealing with code or technical context, provide explanations or solutions that are directly relevant and technically sound. + 7. Maintain clarity and conciseness in your responses while ensuring they are complete and informative. + 8. Use appropriate HTML tags for formatting only when it enhances readability or structure of the response. + 9. Respect privacy and avoid sensationalism when addressing sensitive topics. - Your goal is to provide helpful, appropriate, and concise responses while maintaining ethical standards. + Your goal is to deliver the most relevant, accurate, and helpful response possible, considering both the provided content and your broader understanding. """, ) @@ -261,33 +256,46 @@ class RephraseGrammarEndpoint(BaseAPIView): } def post(self, request, slug): - # Get the task and text input + # Get the task task = request.data.get("task", "grammar_check") - text_input = request.data.get("text_input", "") + + if task == Task.ASK_AI.value: + context = request.data.get("context", "") + user_prompt = request.data.get("text_input", "") + + if not context or not user_prompt: + return Response( + {"error": "Both context and user prompt are required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + text_input = f"Context:\n\n{context}\n\nQuestion: {user_prompt}" + else: + text_input = request.data.get("text_input", "") + + if not text_input: + return Response( + {"error": "Text input is required"}, + status=status.HTTP_400_BAD_REQUEST, + ) # Get casual and formal scores for the tone task casual_score = int(request.data.get("casual_score", 5)) formal_score = int(request.data.get("formal_score", 5)) - # Check the scores - if ( - casual_score + formal_score != 10 - or casual_score < 0 - or formal_score < 0 - ): - return Response( - { - "error": "Invalid scores. casual_score and formal_score must sum to 10 and both must be non-negative." - }, - status=status.HTTP_400_BAD_REQUEST, - ) - - # Check the text input - if not text_input: - return Response( - {"error": "Text input is required"}, - status=status.HTTP_400_BAD_REQUEST, - ) + # Check the scores (only for TONE task) + if task == Task.TONE.value: + if ( + casual_score + formal_score != 10 + or casual_score < 0 + or formal_score < 0 + ): + return Response( + { + "error": "Invalid scores. casual_score and formal_score must sum to 10 and both must be non-negative." + }, + status=status.HTTP_400_BAD_REQUEST, + ) # Get the configuration value OPENAI_API_KEY, GPT_ENGINE = get_configuration_value( @@ -306,7 +314,7 @@ class RephraseGrammarEndpoint(BaseAPIView): # Check the keys if not OPENAI_API_KEY or not GPT_ENGINE: return Response( - {"error": "OpenAI API key and engine is required"}, + {"error": "OpenAI API key and engine are required"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -314,9 +322,7 @@ class RephraseGrammarEndpoint(BaseAPIView): message_list = [{"role": "user", "content": text_input}] # Create the client - client = OpenAI( - api_key=OPENAI_API_KEY, - ) + client = OpenAI(api_key=OPENAI_API_KEY) processed, response = self.get_system_prompt( task, casual_score, formal_score From be8a432b97fbbe96ca1072766bb4cd74cb3542f1 Mon Sep 17 00:00:00 2001 From: Akhil Vamshi Konam Date: Fri, 16 Aug 2024 16:10:45 +0530 Subject: [PATCH 08/10] chore-refactor_askAI --- apiserver/plane/ee/views/app/ai/rephrase.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apiserver/plane/ee/views/app/ai/rephrase.py b/apiserver/plane/ee/views/app/ai/rephrase.py index 79f37d0f1d..76f1e27f9d 100644 --- a/apiserver/plane/ee/views/app/ai/rephrase.py +++ b/apiserver/plane/ee/views/app/ai/rephrase.py @@ -263,6 +263,7 @@ class RephraseGrammarEndpoint(BaseAPIView): context = request.data.get("context", "") user_prompt = request.data.get("text_input", "") + # Check inputs if not context or not user_prompt: return Response( {"error": "Both context and user prompt are required"}, @@ -273,6 +274,7 @@ class RephraseGrammarEndpoint(BaseAPIView): else: text_input = request.data.get("text_input", "") + # Check inputs if not text_input: return Response( {"error": "Text input is required"}, From 867bf395d5c5635ec25d5fb5754e342c47f573f6 Mon Sep 17 00:00:00 2001 From: Akhil Vamshi Konam Date: Fri, 16 Aug 2024 16:18:27 +0530 Subject: [PATCH 09/10] chore-refactor_askAI --- apiserver/plane/ee/views/app/ai/rephrase.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apiserver/plane/ee/views/app/ai/rephrase.py b/apiserver/plane/ee/views/app/ai/rephrase.py index 76f1e27f9d..344b488243 100644 --- a/apiserver/plane/ee/views/app/ai/rephrase.py +++ b/apiserver/plane/ee/views/app/ai/rephrase.py @@ -260,13 +260,13 @@ class RephraseGrammarEndpoint(BaseAPIView): task = request.data.get("task", "grammar_check") if task == Task.ASK_AI.value: - context = request.data.get("context", "") - user_prompt = request.data.get("text_input", "") + context = request.data.get("text_input", "") + user_prompt = request.data.get("query", "") # Check inputs if not context or not user_prompt: return Response( - {"error": "Both context and user prompt are required"}, + {"error": "Query and text input are required"}, status=status.HTTP_400_BAD_REQUEST, ) From 6bc411bc533d566c980b0fdb28440eccc8a48b80 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 21 Aug 2024 13:35:55 +0530 Subject: [PATCH 10/10] chore: added ai options to editor --- .../src/core/components/menus/ai-menu.tsx | 1 + .../editor/src/core/components/menus/index.ts | 1 + .../editor/src/core/extensions/side-menu.tsx | 2 +- packages/editor/src/core/types/ai.ts | 5 +- .../src/ee/extensions/ai-features/handle.ts | 13 - .../src/ee/extensions/ai-features/index.ts | 1 - packages/editor/src/ee/extensions/index.ts | 2 - .../components/pages/editor/editor-body.tsx | 13 +- web/core/services/ai.service.ts | 4 +- .../pages/editor/ai/ask-pi-menu.tsx | 103 ++++++ web/ee/components/pages/editor/ai/index.ts | 2 + web/ee/components/pages/editor/ai/menu.tsx | 306 ++++++++++++++++++ .../components/pages/editor/editor-body.tsx | 23 +- web/ee/components/pages/editor/index.ts | 1 + web/ee/constants/ai.ts | 8 + web/ee/hooks/store/use-flag.ts | 1 + web/ee/hooks/use-editor-flagging.ts | 11 +- 17 files changed, 461 insertions(+), 36 deletions(-) delete mode 100644 packages/editor/src/ee/extensions/ai-features/handle.ts delete mode 100644 packages/editor/src/ee/extensions/ai-features/index.ts create mode 100644 web/ee/components/pages/editor/ai/ask-pi-menu.tsx create mode 100644 web/ee/components/pages/editor/ai/index.ts create mode 100644 web/ee/components/pages/editor/ai/menu.tsx create mode 100644 web/ee/constants/ai.ts diff --git a/packages/editor/src/core/components/menus/ai-menu.tsx b/packages/editor/src/core/components/menus/ai-menu.tsx index 8a714a6552..94c1f8e0ad 100644 --- a/packages/editor/src/core/components/menus/ai-menu.tsx +++ b/packages/editor/src/core/components/menus/ai-menu.tsx @@ -87,6 +87,7 @@ export const AIFeaturesMenu: React.FC = (props) => { >
      {menu?.({ + isOpen: isPopupVisible, onClose: hidePopup, })}
      diff --git a/packages/editor/src/core/components/menus/index.ts b/packages/editor/src/core/components/menus/index.ts index da050b6831..4f8048853c 100644 --- a/packages/editor/src/core/components/menus/index.ts +++ b/packages/editor/src/core/components/menus/index.ts @@ -1,3 +1,4 @@ +export * from "./ai-menu"; export * from "./bubble-menu"; export * from "./ai-menu"; export * from "./block-menu"; diff --git a/packages/editor/src/core/extensions/side-menu.tsx b/packages/editor/src/core/extensions/side-menu.tsx index ccc13ed5eb..75577c74fc 100644 --- a/packages/editor/src/core/extensions/side-menu.tsx +++ b/packages/editor/src/core/extensions/side-menu.tsx @@ -182,7 +182,7 @@ const SideMenu = (options: SideMenuPluginProps) => { aiHandleDOMEvents?.mousemove?.(); } }, - keydown: () => hideSideMenu(), + // keydown: () => hideSideMenu(), mousewheel: () => hideSideMenu(), dragenter: (view) => { if (handlesConfig.dragDrop) { diff --git a/packages/editor/src/core/types/ai.ts b/packages/editor/src/core/types/ai.ts index f5470f51c0..448482e654 100644 --- a/packages/editor/src/core/types/ai.ts +++ b/packages/editor/src/core/types/ai.ts @@ -1,7 +1,8 @@ -type TMenuProps = { +export type TAIMenuProps = { + isOpen: boolean; onClose: () => void; }; export type TAIHandler = { - menu?: (props: TMenuProps) => React.ReactNode; + menu?: (props: TAIMenuProps) => React.ReactNode; }; diff --git a/packages/editor/src/ee/extensions/ai-features/handle.ts b/packages/editor/src/ee/extensions/ai-features/handle.ts deleted file mode 100644 index d477d228ac..0000000000 --- a/packages/editor/src/ee/extensions/ai-features/handle.ts +++ /dev/null @@ -1,13 +0,0 @@ -// extensions -import { SideMenuHandleOptions, SideMenuPluginProps } from "@/extensions"; - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const AIHandlePlugin = (options: SideMenuPluginProps): SideMenuHandleOptions => { - const view = () => {}; - const domEvents = {}; - - return { - view, - domEvents, - }; -}; diff --git a/packages/editor/src/ee/extensions/ai-features/index.ts b/packages/editor/src/ee/extensions/ai-features/index.ts deleted file mode 100644 index af0faafca6..0000000000 --- a/packages/editor/src/ee/extensions/ai-features/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./handle"; diff --git a/packages/editor/src/ee/extensions/index.ts b/packages/editor/src/ee/extensions/index.ts index e62449a05d..77b1b53e69 100644 --- a/packages/editor/src/ee/extensions/index.ts +++ b/packages/editor/src/ee/extensions/index.ts @@ -1,4 +1,2 @@ -export * from "./ai-features"; export * from "./issue-suggestions"; export * from "./document-extensions"; -export * from "./ai-features"; diff --git a/web/core/components/pages/editor/editor-body.tsx b/web/core/components/pages/editor/editor-body.tsx index 1020353b2f..4f7132ca5c 100644 --- a/web/core/components/pages/editor/editor-body.tsx +++ b/web/core/components/pages/editor/editor-body.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useCallback, useEffect } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // document-editor @@ -8,18 +8,20 @@ import { EditorReadOnlyRefApi, EditorRefApi, IMarking, + TAIMenuProps, TDisplayConfig, } from "@plane/editor"; // types import { IUserLite } from "@plane/types"; // components -import { EditorAIMenu } from "@/ce/components/pages/editor/ai"; import { PageContentBrowser, PageEditorTitle, PageContentLoader } from "@/components/pages"; // helpers import { cn } from "@/helpers/common.helper"; // hooks import { useMember, useMention, useUser, useWorkspace } from "@/hooks/store"; import { usePageFilters } from "@/hooks/use-page-filters"; +// plane web components +import { EditorAIMenu } from "@/plane-web/components/pages"; // plane web hooks import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging"; import { useIssueEmbed } from "@/plane-web/hooks/use-issue-embed"; @@ -94,6 +96,11 @@ export const PageEditorBody: React.FC = observer((props) => { fontStyle, }; + const getAIMenu = useCallback( + ({ isOpen, onClose }: TAIMenuProps) => , + [editorRef] + ); + useEffect(() => { updateMarkings(pageDescription ?? "

      "); }, [pageDescription, updateMarkings]); @@ -157,7 +164,7 @@ export const PageEditorBody: React.FC = observer((props) => { }} disabledExtensions={documentEditor} aiHandler={{ - menu: ({ onClose }) => , + menu: getAIMenu, }} /> ) : ( diff --git a/web/core/services/ai.service.ts b/web/core/services/ai.service.ts index ebce95315d..bb0241e06d 100644 --- a/web/core/services/ai.service.ts +++ b/web/core/services/ai.service.ts @@ -1,7 +1,7 @@ -// plane web constants -import { AI_EDITOR_TASKS } from "@/ce/constants/ai"; // helpers import { API_BASE_URL } from "@/helpers/common.helper"; +// plane web constants +import { AI_EDITOR_TASKS } from "@/plane-web/constants/ai"; // services import { APIService } from "@/services/api.service"; // types diff --git a/web/ee/components/pages/editor/ai/ask-pi-menu.tsx b/web/ee/components/pages/editor/ai/ask-pi-menu.tsx new file mode 100644 index 0000000000..d3440ea479 --- /dev/null +++ b/web/ee/components/pages/editor/ai/ask-pi-menu.tsx @@ -0,0 +1,103 @@ +import { useState } from "react"; +import { CircleArrowUp, CornerDownRight, RefreshCcw, Sparkles } from "lucide-react"; +// ui +import { Tooltip } from "@plane/ui"; +// components +import { RichTextReadOnlyEditor } from "@/components/editor"; +// helpers +import { cn } from "@/helpers/common.helper"; + +type Props = { + handleInsertText: (insertOnNextLine: boolean) => void; + handleRegenerate: () => Promise; + isRegenerating: boolean; + response: string | undefined; +}; + +export const AskPiMenu: React.FC = (props) => { + const { handleInsertText, handleRegenerate, isRegenerating, response } = props; + // states + const [query, setQuery] = useState(""); + + return ( + <> +
      + + + + {response ? ( +
      + +
      + + + + + + + +
      +
      + ) : ( +

      Pi is answering...

      + )} +
      +
      +
      + + + + setQuery(e.target.value)} + placeholder="Tell Pi what to do..." + /> + + + +
      +
      + + ); +}; diff --git a/web/ee/components/pages/editor/ai/index.ts b/web/ee/components/pages/editor/ai/index.ts new file mode 100644 index 0000000000..d21eb63d70 --- /dev/null +++ b/web/ee/components/pages/editor/ai/index.ts @@ -0,0 +1,2 @@ +export * from "./ask-pi-menu"; +export * from "./menu"; diff --git a/web/ee/components/pages/editor/ai/menu.tsx b/web/ee/components/pages/editor/ai/menu.tsx new file mode 100644 index 0000000000..3422c73c25 --- /dev/null +++ b/web/ee/components/pages/editor/ai/menu.tsx @@ -0,0 +1,306 @@ +"use client"; + +import React, { RefObject, useEffect, useRef, useState } from "react"; +import { useParams } from "next/navigation"; +import { ChevronRight, CornerDownRight, LucideIcon, PencilLine, RefreshCcw, Sparkles } from "lucide-react"; +// plane editor +import { EditorRefApi } from "@plane/editor"; +// plane ui +import { Tooltip } from "@plane/ui"; +// components +import { RichTextReadOnlyEditor } from "@/components/editor"; +// helpers +import { cn } from "@/helpers/common.helper"; +// plane web constants +import { AI_EDITOR_TASKS } from "@/plane-web/constants/ai"; +// services +import { AIService, TTaskPayload } from "@/services/ai.service"; +const aiService = new AIService(); + +type Props = { + editorRef: RefObject; + isOpen: boolean; + onClose: () => void; +}; + +const MENU_ITEMS: { + icon: LucideIcon; + key: AI_EDITOR_TASKS; + label: string; +}[] = [ + { + key: AI_EDITOR_TASKS.PARAPHRASE, + icon: PencilLine, + label: "Paraphrase", + }, + { + key: AI_EDITOR_TASKS.SIMPLIFY, + icon: PencilLine, + label: "Simplify", + }, + { + key: AI_EDITOR_TASKS.ELABORATE, + icon: PencilLine, + label: "Elaborate", + }, + { + key: AI_EDITOR_TASKS.SUMMARIZE, + icon: PencilLine, + label: "Summarize", + }, + { + key: AI_EDITOR_TASKS.GET_TITLE, + icon: PencilLine, + label: "Get title", + }, + // { + // key: AI_EDITOR_TASKS.TONE, + // icon: PencilLine, + // label: "Tone adjustment", + // }, +]; + +const LOADING_TEXTS: { + [key in AI_EDITOR_TASKS]: string; +} = { + [AI_EDITOR_TASKS.PARAPHRASE]: "Pi is paraphrasing", + [AI_EDITOR_TASKS.SIMPLIFY]: "Pi is simplifying", + [AI_EDITOR_TASKS.ELABORATE]: "Pi is elaborating", + [AI_EDITOR_TASKS.SUMMARIZE]: "Pi is summarizing", + [AI_EDITOR_TASKS.GET_TITLE]: "Pi is getting title", + [AI_EDITOR_TASKS.TONE]: "Pi is adjusting tone", +}; + +const TONES_LIST = [ + { + key: "default", + label: "Default", + casual_score: 5, + formal_score: 5, + }, + { + key: "professional", + label: "💼 Professional", + casual_score: 0, + formal_score: 10, + }, + { + key: "casual", + label: "😃 Casual", + casual_score: 10, + formal_score: 0, + }, +]; + +export const EditorAIMenu: React.FC = (props) => { + const { editorRef, isOpen, onClose } = props; + // states + const [activeTask, setActiveTask] = useState(null); + const [response, setResponse] = useState(undefined); + const [isRegenerating, setIsRegenerating] = useState(false); + // refs + const responseContainerRef = useRef(null); + // params + const { workspaceSlug } = useParams(); + const handleGenerateResponse = async (payload: TTaskPayload) => { + if (!workspaceSlug) return; + await aiService.performEditorTask(workspaceSlug.toString(), payload).then((res) => setResponse(res.response)); + }; + // handle task click + const handleClick = async (key: AI_EDITOR_TASKS) => { + const selection = editorRef.current?.getSelectedText(); + if (!selection || activeTask === key) return; + setActiveTask(key); + setResponse(undefined); + setIsRegenerating(false); + await handleGenerateResponse({ + task: key, + text_input: selection, + }); + }; + // handle re-generate response + const handleRegenerate = async () => { + const selection = editorRef.current?.getSelectedText(); + if (!selection || !activeTask) return; + setIsRegenerating(true); + await handleGenerateResponse({ + task: activeTask, + text_input: selection, + }) + .then(() => + responseContainerRef.current?.scrollTo({ + top: 0, + behavior: "smooth", + }) + ) + .finally(() => setIsRegenerating(false)); + }; + // handle re-generate response + const handleToneChange = async (key: string) => { + const selectedTone = TONES_LIST.find((t) => t.key === key); + const selection = editorRef.current?.getSelectedText(); + if (!selectedTone || !selection || !activeTask) return; + setResponse(undefined); + setIsRegenerating(false); + await handleGenerateResponse({ + casual_score: selectedTone.casual_score, + formal_score: selectedTone.formal_score, + task: activeTask, + text_input: selection, + }).then(() => + responseContainerRef.current?.scrollTo({ + top: 0, + behavior: "smooth", + }) + ); + }; + // handle replace selected text with the response + const handleInsertText = (insertOnNextLine: boolean) => { + if (!response) return; + editorRef.current?.insertText(response, insertOnNextLine); + onClose(); + }; + + // reset on close + useEffect(() => { + if (!isOpen) { + setActiveTask(null); + setResponse(undefined); + } + }, [isOpen]); + + return ( +
      +
      + {MENU_ITEMS.map((item) => { + const isActiveTask = activeTask === item.key; + + return ( + + ); + })} +
      +
      +
      + + + + {response ? ( +
      + +
      + + + + + + + +
      +
      + ) : ( +

      + {activeTask ? LOADING_TEXTS[activeTask] : "Pi is writing"}... +

      + )} +
      +
      + {TONES_LIST.map((tone) => ( + + ))} +
      +
      +
      + ); +}; diff --git a/web/ee/components/pages/editor/editor-body.tsx b/web/ee/components/pages/editor/editor-body.tsx index bd0dc9264c..32b7519b0e 100644 --- a/web/ee/components/pages/editor/editor-body.tsx +++ b/web/ee/components/pages/editor/editor-body.tsx @@ -1,4 +1,4 @@ -import { useEffect } from "react"; +import { useCallback, useEffect } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; // document-editor @@ -8,6 +8,8 @@ import { EditorReadOnlyRefApi, EditorRefApi, IMarking, + TAIMenuProps, + TDisplayConfig, } from "@plane/editor"; // types import { IUserLite } from "@plane/types"; @@ -19,7 +21,7 @@ import { cn } from "@/helpers/common.helper"; import { useMember, useUser, useWorkspace } from "@/hooks/store"; import { usePageFilters } from "@/hooks/use-page-filters"; // plane web components -import { IssueEmbedCard } from "@/plane-web/components/pages"; +import { EditorAIMenu, IssueEmbedCard } from "@/plane-web/components/pages"; // plane web hooks import { useEditorFlagging } from "@/plane-web/hooks/use-editor-flagging"; import { useWorkspaceIssueEmbed } from "@/plane-web/hooks/use-workspace-issue-embed"; @@ -84,10 +86,20 @@ export const WorkspacePageEditorBody: React.FC = observer((props) => { // editor flaggings const { documentEditor } = useEditorFlagging(workspaceSlug?.toString()); // page filters - const { isFullWidth } = usePageFilters(); + const { fontSize, fontStyle, isFullWidth } = usePageFilters(); // issue-embed const { fetchIssues } = useWorkspaceIssueEmbed(workspaceSlug?.toString() ?? ""); + const displayConfig: TDisplayConfig = { + fontSize, + fontStyle, + }; + + const getAIMenu = useCallback( + ({ isOpen, onClose }: TAIMenuProps) => , + [editorRef] + ); + useEffect(() => { updateMarkings(pageDescription ?? "

      "); }, [pageDescription, updateMarkings]); @@ -145,6 +157,7 @@ export const WorkspacePageEditorBody: React.FC = observer((props) => { value={pageDescriptionYJS} ref={editorRef} containerClassName="p-0 pb-64" + displayConfig={displayConfig} editorClassName="pl-10" onChange={handleDescriptionChange} mentionHandler={{ @@ -183,6 +196,9 @@ export const WorkspacePageEditorBody: React.FC = observer((props) => { }, }} disabledExtensions={documentEditor} + aiHandler={{ + menu: getAIMenu, + }} /> ) : ( = observer((props) => { initialValue={pageDescription ?? "

      "} handleEditorReady={handleReadOnlyEditorReady} containerClassName="p-0 pb-64 border-none" + displayConfig={displayConfig} editorClassName="pl-10" mentionHandler={{ highlights: mentionHighlights, diff --git a/web/ee/components/pages/editor/index.ts b/web/ee/components/pages/editor/index.ts index c0f6b87d5c..c7ed8ae542 100644 --- a/web/ee/components/pages/editor/index.ts +++ b/web/ee/components/pages/editor/index.ts @@ -1,3 +1,4 @@ +export * from "./ai"; export * from "./embed"; export * from "./editor-body"; export * from "./page-root"; diff --git a/web/ee/constants/ai.ts b/web/ee/constants/ai.ts new file mode 100644 index 0000000000..ae1ead816b --- /dev/null +++ b/web/ee/constants/ai.ts @@ -0,0 +1,8 @@ +export enum AI_EDITOR_TASKS { + PARAPHRASE = "PARAPHRASE", + SIMPLIFY = "SIMPLIFY", + ELABORATE = "ELABORATE", + SUMMARIZE = "SUMMARIZE", + GET_TITLE = "GET_TITLE", + TONE = "TONE", +} diff --git a/web/ee/hooks/store/use-flag.ts b/web/ee/hooks/store/use-flag.ts index de5baa0c05..c921372ee4 100644 --- a/web/ee/hooks/store/use-flag.ts +++ b/web/ee/hooks/store/use-flag.ts @@ -5,6 +5,7 @@ import { StoreContext } from "@/lib/store-context"; export enum E_FEATURE_FLAGS { BULK_OPS = "BULK_OPS", BULK_OPS_ADVANCED = "BULK_OPS_ADVANCED", + EDITOR_AI_OPS = "EDITOR_AI_OPS", ESTIMATE_WITH_TIME = "ESTIMATE_WITH_TIME", ISSUE_TYPE_DISPLAY = "ISSUE_TYPE_DISPLAY", ISSUE_TYPE_SETTINGS = "ISSUE_TYPE_SETTINGS", diff --git a/web/ee/hooks/use-editor-flagging.ts b/web/ee/hooks/use-editor-flagging.ts index 51ff212940..b07b93fbfc 100644 --- a/web/ee/hooks/use-editor-flagging.ts +++ b/web/ee/hooks/use-editor-flagging.ts @@ -5,13 +5,6 @@ import { useFlag } from "@/plane-web/hooks/store"; /** * @description extensions disabled in various editors - * @returns - * ```ts - * { - * documentEditor: TExtensions[] - * richTextEditor: TExtensions[] - * } - * ``` */ export const useEditorFlagging = ( workspaceSlug: string @@ -20,11 +13,11 @@ export const useEditorFlagging = ( richTextEditor: TExtensions[]; } => { const isIssueEmbedEnabled = useFlag(workspaceSlug, "PAGE_ISSUE_EMBEDS"); + const isEditorAIOpsEnabled = useFlag(workspaceSlug, "EDITOR_AI_OPS"); // extensions disabled in the document editor const documentEditor: TExtensions[] = []; if (!isIssueEmbedEnabled) documentEditor.push("issue-embed"); - // Temporarily disabled - documentEditor.push("ai"); + if (!isEditorAIOpsEnabled) documentEditor.push("ai"); return { documentEditor,