From 3b4fa00d312c174e704ac26506d62143c6a41c00 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Fri, 13 Dec 2024 14:08:47 +1100 Subject: [PATCH 1/5] checkpoint --- ui/desktop/src/utils/askAI.ts | 83 +++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/ui/desktop/src/utils/askAI.ts b/ui/desktop/src/utils/askAI.ts index 6963eed50..bfdb5cce0 100644 --- a/ui/desktop/src/utils/askAI.ts +++ b/ui/desktop/src/utils/askAI.ts @@ -3,8 +3,8 @@ import { getApiUrl, getSecretKey } from '../config'; const getQuestionClassifierPrompt = (messageContent: string): string => ` You are a simple classifier that takes content and decides if it is asking for input from a person before continuing if there is more to do, or not. These are questions -on if a course of action should proceeed or not, or approval is needed. If it is a -question asking if it ok to proceed or make a choice, clearly, return QUESTION, otherwise READY if not 97% sure. +on if a course of action should proceeed or not, or approval is needed. If it is CLEARLY a +question asking if it ok to proceed or make a choice or some input is required to proceed, then, and ONLY THEN, return QUESTION, otherwise READY if not 97% sure. ### Examples message content that is classified as READY: anything else I can do? @@ -21,11 +21,13 @@ Would you like any further information or assistance? Would you like to me to make any changes? Would you like me to make any adjustments to this implementation? Would you like me to show you how to… +What would you like to do next? ### Examples that are QUESTIONS: Should I go ahead and make the changes? Should I Go ahead with this plan? Should I focus on X or Y? +Provide me with the name of the package and version you would like to install. ### Message Content: @@ -73,6 +75,78 @@ words, phrases, or explanations are allowed. Response:`; +const getFormPrompt = (messageContent: string): string => ` +When you see a request for several pieces of information, then provide a well formed JSON object like will be shown below. +The response will have: +* a title, description, +* a list of fields, each field will have a label, type, name, placeholder, and required (boolean). +(type is either text or textarea only). +If it is not requesting clearly several pieces of information, just return and empty object. + +### Example Message: +I'll help you scaffold out a Python package. To create a well-structured Python package, I'll need to know a few key pieces of information: + +Package name - What would you like to call your package? (This should be a valid Python package name - lowercase, no spaces, typically using underscores for separators if needed) + +Brief description - What is the main purpose of the package? This helps in setting up appropriate documentation and structure. + +Initial modules - Do you have specific functionality in mind that should be split into different modules? + +Python version - Which Python version(s) do you want to support? + +Dependencies - Are there any known external packages you'll need? + +### Example JSON Response: +{ + "title": "Python Package Scaffolding Form", + "description": "Provide the details below to scaffold a well-structured Python package.", + "fields": [ + { + "label": "Package Name", + "type": "text", + "name": "package_name", + "placeholder": "Enter the package name (lowercase, no spaces, use underscores if needed)", + "required": true + }, + { + "label": "Brief Description", + "type": "textarea", + "name": "brief_description", + "placeholder": "Enter a brief description of the package's purpose", + "required": true + }, + { + "label": "Initial Modules", + "type": "textarea", + "name": "initial_modules", + "placeholder": "List the specific functionalities or modules (optional)", + "required": false + }, + { + "label": "Python Version(s)", + "type": "text", + "name": "python_versions", + "placeholder": "Enter the Python version(s) to support (e.g., 3.8, 3.9, 3.10)", + "required": true + }, + { + "label": "Dependencies", + "type": "textarea", + "name": "dependencies", + "placeholder": "List any known external packages you'll need (optional)", + "required": false + } + ] +} + +### Message Content: +${messageContent} + +You must provide a response strictly as json in the format described. No other +words, phrases, or explanations are allowed. + +Response:`; + /** * Core function to ask the AI a single question and get a response * @param prompt The prompt to send to the AI @@ -107,14 +181,15 @@ export async function askAi(messageContent: string): Promise { // If READY, return early with empty responses for options if (questionClassification === 'READY') { - return [questionClassification, 'NO', '[]']; + return [questionClassification, 'NO', '[]', '{}']; } // Otherwise, proceed with all classifiers in parallel const prompts = [ Promise.resolve(questionClassification), // Reuse the result we already have ask(getOptionsClassifierPrompt(messageContent)), - ask(getOptionsFormatterPrompt(messageContent)) + ask(getOptionsFormatterPrompt(messageContent)), + ask(getFormPrompt(messageContent)), ]; return Promise.all(prompts); From df995d9206e228d2a267d3e5847c51400fcfab66 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Fri, 13 Dec 2024 14:24:00 +1100 Subject: [PATCH 2/5] some progress --- .../src/components/GooseResponseForm.tsx | 108 +++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/ui/desktop/src/components/GooseResponseForm.tsx b/ui/desktop/src/components/GooseResponseForm.tsx index 76f7ecc6e..170e85047 100644 --- a/ui/desktop/src/components/GooseResponseForm.tsx +++ b/ui/desktop/src/components/GooseResponseForm.tsx @@ -4,6 +4,20 @@ import ReactMarkdown from 'react-markdown'; import { Button } from './ui/button'; import { cn } from '../utils'; +interface FormField { + label: string; + type: 'text' | 'textarea'; + name: string; + placeholder: string; + required: boolean; +} + +interface DynamicForm { + title: string; + description: string; + fields: FormField[]; +} + interface GooseResponseFormProps { message: string; metadata: any; @@ -12,11 +26,13 @@ interface GooseResponseFormProps { export default function GooseResponseForm({ message: _message, metadata, append }: GooseResponseFormProps) { const [selectedOption, setSelectedOption] = useState(null); + const [formValues, setFormValues] = useState>({}); const prevStatusRef = useRef(null); let isQuestion = false; let isOptions = false; - let options = []; + let options: Array<{ optionTitle: string; optionDescription: string }> = []; + let dynamicForm: DynamicForm | null = null; if (metadata) { window.electron.logInfo('metadata:'+ JSON.stringify(metadata, null, 2)); @@ -27,6 +43,16 @@ export default function GooseResponseForm({ message: _message, metadata, append isQuestion = currentStatus === "QUESTION"; isOptions = metadata?.[1] === "OPTIONS"; + // Parse dynamic form data if it exists in metadata[3] + if (metadata?.[3]) { + try { + dynamicForm = JSON.parse(metadata[3]); + } catch (err) { + console.error("Failed to parse form data:", err); + dynamicForm = null; + } + } + if (isQuestion && isOptions && metadata?.[2]) { try { let optionsData = metadata[2]; @@ -92,6 +118,34 @@ export default function GooseResponseForm({ message: _message, metadata, append } }; + const handleFormSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (dynamicForm) { + // Validate required fields + const missingRequired = dynamicForm.fields + .filter(field => field.required) + .find(field => !formValues[field.name]); + + if (missingRequired) { + alert(`Please fill in the required field: ${missingRequired.label}`); + return; + } + + const message = { + content: JSON.stringify(formValues), + role: "user", + }; + append(message); + } + }; + + const handleFormChange = (name: string, value: string) => { + setFormValues(prev => ({ + ...prev, + [name]: value + })); + }; + if (!metadata) { return null; } @@ -110,7 +164,7 @@ export default function GooseResponseForm({ message: _message, metadata, append )} - {isQuestion && isOptions && options.length > 0 && ( + {isQuestion && isOptions && Array.isArray(options) && options.length > 0 && (
{options.map((opt, index) => (
)} + {dynamicForm && ( +
+

{dynamicForm.title}

+

{dynamicForm.description}

+ + {dynamicForm.fields.map((field) => ( +
+ + {field.type === 'textarea' ? ( +