diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..db308ec --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,57 @@ +linters-settings: + errcheck: + check-type-assertions: true + goconst: + min-len: 2 + min-occurrences: 3 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + govet: + check-shadowing: true + nolintlint: + require-explanation: true + require-specific: true + +linters: + disable-all: true + enable: + - bodyclose + #- depguard + - dogsled + #- dupl + - errcheck + - exportloopref + - exhaustive + #- goconst TODO + - gofmt + - goimports + #- gomnd + - gocyclo + - gosec + - gosimple + - govet + - ineffassign + - misspell + #- nolintlint + - nakedret + - prealloc + - predeclared + #- revive #TODO + - staticcheck + - stylecheck + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - whitespace + - wsl + +run: + issues-exit-code: 1 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5e0a611 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +PROJECTNAME=$(shell basename "$(PWD)") + +# Go related variables. +# Make is verbose in Linux. Make it silent. +MAKEFLAGS += --silent + +.PHONY: setup +## setup: Setup installes dependencies +setup: + @go mod tidy + +.PHONY: lint +## test: Runs the linter +lint: + @golangci-lint run --color=always --sort-results ./... + +.PHONY: run +## run: Runs awsrecon +run: + @go run -race main.go -h + +.PHONY: test +## test: Runs go test with default values +test: + @go test -v -race -count=1 -coverprofile=coverage.out ./... + +.PHONY: help +## help: Prints this help message +help: Makefile + @echo + @echo " Choose a command run in "$(PROJECTNAME)":" + @echo + @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' + @echo \ No newline at end of file diff --git a/README.md b/README.md index d59fb88..12f75f3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,104 @@ -# go-genie -👻 go-genie +# 👻 genie +![Build Status](https://github.com/hupe1980/genie/workflows/build/badge.svg) +[![Go Reference](https://pkg.go.dev/badge/github.com/hupe1980/genie.svg)](https://pkg.go.dev/github.com/hupe1980/genie) +> Genie is a Proof of Concept (POC) source code generator that showcases the potential of utilizing Large Language Models (LLMs) for code generation. As a limited prototype, Genie provides a glimpse into the capabilities of LLM-based code generation tools. It allows users to experiment with generating source code based on simplified prompts or descriptions. + +Genie is based on https://github.com/smol-ai/developer. + +## How to use +```text +Usage: + genie [flags] + +Examples: +genie -p "Create a python hello world" +genie -p prompt.txt + +Flags: + --api-key string openAI api key + -h, --help help for genie + -m, --model string model to use (default "gpt-3.5-turbo") + -o, --outdir string outdir to use (default "dist") + -p, --prompt string prompt to use (required) + -t, --temperature float32 temperature to use (default 0.4) + -v, --version version for genie +``` +## Example +```bash +genie -p _examples/aws_cdk/prompt.md -o _examples/aws_cdk/dist +``` + +Outputs: +```text + + ██████ ███████ ███ ██ ██ ███████ +██ ██ ████ ██ ██ ██ +██ ███ █████ ██ ██ ██ ██ █████ +██ ██ ██ ██ ██ ██ ██ ██ + ██████ ███████ ██ ████ ██ ███████ + +Create list of files: +• src/api/api.ts +• src/api/todoController.ts +• src/lambda/todoHandler.ts +• src/dynamoDB/todoTable.ts +• .env +• .gitignore +• README.md +• cdk.json +• jest.config.js +• package.json +• projenrc.json +• tsconfig.json + +Reasoning: +• The app requires an API Gateway to expose the CRUD endpoints. +• The Lambda functions will handle the API requests and interact with DynamoDB, so we need to create a Lambda Backend. +• DynamoDB is needed to store the todo items. +• We'll use 'projen' to manage the AWS CDK project and its dependencies. +• To implement the CRUD operations, we'll need to create the necessary API endpoints. + +Create list of shared Dependecies: +• {dataSchemas Shared data schemas for the request and response bodies [TodoItem CreateTodoRequest CreateTodoResponse GetAllTodosResponse GetTodoByIdResponse UpdateTodoRequest UpdateTodoResponse]} +• {environmentVariables Shared environment variables for configuring DynamoDB table name and settings [DYNAMODB_TABLE_NAME]} +• {errorHandlingUtilities Shared utilities for error handling [createErrorResponse]} +• {awsCdkLibraries Shared AWS CDK related libraries [aws_cdk aws_lambda aws_apigateway aws_dynamodb]} + +Reasoning: +• To identify the shared dependencies between the generated files, we need to analyze the content of each file and look for common symbols or entities used across multiple files. +• Starting with the API endpoints, there might be shared data schemas for the request and response bodies. +• The todoController.ts file is responsible for handling the API requests and might have dependencies on the data schemas. +• The todoHandler.ts file implements the Lambda functions for CRUD operations and might have dependencies on the data schemas as well. +• The todoTable.ts file creates the DynamoDB table and might have dependencies on the data schemas, as it needs to define the primary key. +• Other shared dependencies could include environment variables, error handling utilities, and AWS CDK related libraries. +• We'll analyze the content of each file and identify the shared symbols to create the shared_dependencies list. + +File src/lambda/todoHandler.ts created +File src/dynamoDB/todoTable.ts created +File .env created +File src/api/api.ts created +File .gitignore created +File src/api/todoController.ts created +File cdk.json created +File jest.config.js created +File package.json created +File tsconfig.json created +File projenrc.json created +File README.md created + +Tokens Used: 28404 +Prompt Tokens: 24452 +Completion Tokens: 3952 +Successful Requests: 14 +Total Cost (USD): $0.00 +``` + +## Contributing +Contributions are welcome! Feel free to open an issue or submit a pull request for any improvements or new features you would like to see. + +## References +- https://github.com/smol-ai/developer +- https://github.com/hupe1980/golc + +## License +This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details. \ No newline at end of file diff --git a/_examples/aws_cdk/dist/.env b/_examples/aws_cdk/dist/.env new file mode 100644 index 0000000..44f2992 --- /dev/null +++ b/_examples/aws_cdk/dist/.env @@ -0,0 +1 @@ +DYNAMODB_TABLE_NAME=myTodoTable \ No newline at end of file diff --git a/_examples/aws_cdk/dist/.gitignore b/_examples/aws_cdk/dist/.gitignore new file mode 100644 index 0000000..fbf081a --- /dev/null +++ b/_examples/aws_cdk/dist/.gitignore @@ -0,0 +1,17 @@ +# .gitignore + +# Node.js +node_modules +package-lock.json + +# AWS CDK +.cdk.staging +.cdk.out + +# CDK generated files +cdk.out +CDKOutputs.json + +# IDE +.idea +.vscode \ No newline at end of file diff --git a/_examples/aws_cdk/dist/README.md b/_examples/aws_cdk/dist/README.md new file mode 100644 index 0000000..f4f2b63 --- /dev/null +++ b/_examples/aws_cdk/dist/README.md @@ -0,0 +1,131 @@ +# AWS CDK Todo App Specification + +## Overview + +This specification outlines the requirements for an AWS CDK (Cloud Development Kit) app that implements a CRUD (Create, Read, Update, Delete) API for managing todo items. The app will consist of the following components: + +1. API Gateway: Exposes HTTP endpoints to interact with the backend Lambda functions. +2. Lambda Backend: Responsible for handling API requests and interacting with the DynamoDB. +3. DynamoDB: A managed NoSQL database used to store todo items. + +The project will be managed using `projen`, a project generator tool that simplifies the setup and configuration of AWS CDK projects. + +## Requirements + +The app should satisfy the following requirements: + +1. API Gateway: + - Expose HTTP endpoints for CRUD operations on todo items. + - Use AWS Lambda as the integration for API endpoints. + - Utilize API Gateway REST API. + - Implement proper error handling and validation. + +2. Lambda Backend: + - Implement Lambda functions to handle CRUD operations for todo items. + - Ensure secure access to DynamoDB tables. + - Handle proper error responses and validations. + - Use environment variables to configure the DynamoDB table name and other settings. + +3. DynamoDB: + - Create a DynamoDB table to store todo items. + - The table should have a primary key to uniquely identify each todo item. + +4. Projen: + - Use `projen` to manage the AWS CDK project and its dependencies. + - Configure common project settings such as TypeScript, Jest, and AWS CDK. + +## API Endpoints + +1. Create a Todo Item + + Endpoint: `POST /todos` + + Request Body: + ``` + { + "title": "string", + "description": "string" + } + ``` + + Response: + - Status: 201 Created + - Body: + ``` + { + "id": "string", + "title": "string", + "description": "string", + "createdAt": "string", + "updatedAt": "string" + } + ``` + +2. Get All Todo Items + + Endpoint: `GET /todos` + + Response: + - Status: 200 OK + - Body: + ``` + [ + { + "id": "string", + "title": "string", + "description": "string", + "createdAt": "string", + "updatedAt": "string" + }, + ... + ] + ``` + +3. Get a Todo Item by ID + + Endpoint: `GET /todos/{id}` + + Response: + - Status: 200 OK + - Body: + ``` + { + "id": "string", + "title": "string", + "description": "string", + "createdAt": "string", + "updatedAt": "string" + } + ``` + +4. Update a Todo Item + + Endpoint: `PUT /todos/{id}` + + Request Body: + ``` + { + "title": "string", + "description": "string" + } + ``` + + Response: + - Status: 200 OK + - Body: + ``` + { + "id": "string", + "title": "string", + "description": "string", + "createdAt": "string", + "updatedAt": "string" + } + ``` + +5. Delete a Todo Item + + Endpoint: `DELETE /todos/{id}` + + Response: + - Status: 204 No Content \ No newline at end of file diff --git a/_examples/aws_cdk/dist/cdk.json b/_examples/aws_cdk/dist/cdk.json new file mode 100644 index 0000000..709c86a --- /dev/null +++ b/_examples/aws_cdk/dist/cdk.json @@ -0,0 +1,21 @@ +{ + "app": "npx ts-node bin/todo-app.ts", + "context": { + "aws:cdk:enable-path-metadata": "true", + "aws:cdk:path-metadata-experimental": "true" + }, + "output": "dist", + "context": { + "availabilityZones": "us-east-1a,us-east-1b,us-east-1c", + "aws-cdk:enable-asset-metadata": "false", + "aws-cdk:enable-role-usage-report": "true" + }, + "watch": true, + "language": "typescript", + "context": { + "aws-cdk:enable-privacy-mode": "true" + }, + "context": { + "aws-cdk:enable-stack-trace": "true" + } +} \ No newline at end of file diff --git a/_examples/aws_cdk/dist/jest.config.js b/_examples/aws_cdk/dist/jest.config.js new file mode 100644 index 0000000..2547363 --- /dev/null +++ b/_examples/aws_cdk/dist/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: "node", + transform: { + "^.+\\.tsx?$": "ts-jest", + }, + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], + testMatch: ["**/*.test.ts"], +}; \ No newline at end of file diff --git a/_examples/aws_cdk/dist/package.json b/_examples/aws_cdk/dist/package.json new file mode 100644 index 0000000..a711e9e --- /dev/null +++ b/_examples/aws_cdk/dist/package.json @@ -0,0 +1,20 @@ +{ + "name": "aws-cdk-todo-app", + "version": "0.1.0", + "scripts": { + "test": "jest", + "build": "tsc", + "cdk": "cdk" + }, + "devDependencies": { + "aws-cdk-libraries": "*", + "jest": "*", + "ts-jest": "*", + "typescript": "*", + "projen": "*" + }, + "dependencies": { + "aws-sdk": "*", + "uuid": "*" + } +} \ No newline at end of file diff --git a/_examples/aws_cdk/dist/projenrc.json b/_examples/aws_cdk/dist/projenrc.json new file mode 100644 index 0000000..ff913e9 --- /dev/null +++ b/_examples/aws_cdk/dist/projenrc.json @@ -0,0 +1,35 @@ +const { AwsCdkTypeScriptApp } = require('projen'); + +const project = new AwsCdkTypeScriptApp({ + cdkVersion: '1.95.2', + defaultReleaseBranch: 'main', + name: 'aws-cdk-todo-app', + cdkDependencies: [ + 'aws-lambda', + 'aws-lambda-event-types', + 'aws-dynamodb', + 'aws-apigateway', + ], + cdkTestDependencies: [ + '@types/aws-lambda', + '@types/aws-dynamodb', + '@types/aws-apigateway', + ], + deps: [ + 'uuid', + ], + devDeps: [ + 'esbuild', + ], + gitignore: [ + '.env', + 'cdk.context.json', + ], + tsconfig: { + compilerOptions: { + lib: ['es2020', 'dom'], + }, + }, +}); + +project.synth(); diff --git a/_examples/aws_cdk/dist/src/api/api.ts b/_examples/aws_cdk/dist/src/api/api.ts new file mode 100644 index 0000000..4b708d0 --- /dev/null +++ b/_examples/aws_cdk/dist/src/api/api.ts @@ -0,0 +1,74 @@ +import * as aws_cdk from 'aws-cdk-lib'; +import * as aws_apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as aws_lambda from 'aws-cdk-lib/aws-lambda'; +import { DYNAMODB_TABLE_NAME } from '../environmentVariables'; + +export class TodoApi { + constructor(scope: aws_cdk.Construct) { + // Create Lambda function for CreateTodo + const createTodoLambda = new aws_lambda.Function(scope, 'CreateTodoLambda', { + runtime: aws_lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: aws_lambda.Code.fromAsset('lambda'), + environment: { + DYNAMODB_TABLE_NAME: DYNAMODB_TABLE_NAME, + }, + }); + + // Create Lambda function for GetAllTodos + const getAllTodosLambda = new aws_lambda.Function(scope, 'GetAllTodosLambda', { + runtime: aws_lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: aws_lambda.Code.fromAsset('lambda'), + environment: { + DYNAMODB_TABLE_NAME: DYNAMODB_TABLE_NAME, + }, + }); + + // Create Lambda function for GetTodoById + const getTodoByIdLambda = new aws_lambda.Function(scope, 'GetTodoByIdLambda', { + runtime: aws_lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: aws_lambda.Code.fromAsset('lambda'), + environment: { + DYNAMODB_TABLE_NAME: DYNAMODB_TABLE_NAME, + }, + }); + + // Create Lambda function for UpdateTodo + const updateTodoLambda = new aws_lambda.Function(scope, 'UpdateTodoLambda', { + runtime: aws_lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: aws_lambda.Code.fromAsset('lambda'), + environment: { + DYNAMODB_TABLE_NAME: DYNAMODB_TABLE_NAME, + }, + }); + + // Create Lambda function for DeleteTodo + const deleteTodoLambda = new aws_lambda.Function(scope, 'DeleteTodoLambda', { + runtime: aws_lambda.Runtime.NODEJS_14_X, + handler: 'index.handler', + code: aws_lambda.Code.fromAsset('lambda'), + environment: { + DYNAMODB_TABLE_NAME: DYNAMODB_TABLE_NAME, + }, + }); + + // Create API Gateway REST API + const api = new aws_apigateway.RestApi(scope, 'TodoApi', { + restApiName: 'Todo API', + }); + + // Create API Gateway resources + const todosResource = api.root.addResource('todos'); + const todoResource = todosResource.addResource('{id}'); + + // Create API Gateway methods + todosResource.addMethod('POST', new aws_apigateway.LambdaIntegration(createTodoLambda)); + todosResource.addMethod('GET', new aws_apigateway.LambdaIntegration(getAllTodosLambda)); + todoResource.addMethod('GET', new aws_apigateway.LambdaIntegration(getTodoByIdLambda)); + todoResource.addMethod('PUT', new aws_apigateway.LambdaIntegration(updateTodoLambda)); + todoResource.addMethod('DELETE', new aws_apigateway.LambdaIntegration(deleteTodoLambda)); + } +} \ No newline at end of file diff --git a/_examples/aws_cdk/dist/src/api/todoController.ts b/_examples/aws_cdk/dist/src/api/todoController.ts new file mode 100644 index 0000000..b8be673 --- /dev/null +++ b/_examples/aws_cdk/dist/src/api/todoController.ts @@ -0,0 +1,129 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import { createErrorResponse } from '../errorHandlingUtilities'; +import { + CreateTodoRequest, + CreateTodoResponse, + UpdateTodoRequest, + UpdateTodoResponse, + GetTodoByIdResponse, + GetAllTodosResponse, +} from '../dataSchemas'; + +export class TodoController { + async createTodo( + event: APIGatewayProxyEvent + ): Promise { + try { + const { title, description } = JSON.parse(event.body || '{}') as CreateTodoRequest; + + // Code to create a new todo item in the database + + const todo: CreateTodoResponse = { + id: 'your_todo_id', + title: title, + description: description, + createdAt: 'todo_created_at', + updatedAt: 'todo_updated_at', + }; + + return { + statusCode: 201, + body: JSON.stringify(todo), + }; + } catch (error) { + return createErrorResponse(error); + } + } + + async getAllTodos(): Promise { + try { + // Code to retrieve all todo items from the database + + const todos: GetAllTodosResponse[] = []; + + // Sample todo item + todos.push({ + id: 'your_todo_id', + title: 'Sample Todo', + description: 'This is a sample todo item.', + createdAt: 'todo_created_at', + updatedAt: 'todo_updated_at', + }); + + return { + statusCode: 200, + body: JSON.stringify(todos), + }; + } catch (error) { + return createErrorResponse(error); + } + } + + async getTodoById( + event: APIGatewayProxyEvent + ): Promise { + try { + const todoId = event.pathParameters?.id || ''; + + // Code to retrieve the todo item from the database + + const todo: GetTodoByIdResponse = { + id: todoId, + title: 'Sample Todo', + description: 'This is a sample todo item.', + createdAt: 'todo_created_at', + updatedAt: 'todo_updated_at', + }; + + return { + statusCode: 200, + body: JSON.stringify(todo), + }; + } catch (error) { + return createErrorResponse(error); + } + } + + async updateTodo( + event: APIGatewayProxyEvent + ): Promise { + try { + const todoId = event.pathParameters?.id || ''; + const { title, description } = JSON.parse(event.body || '{}') as UpdateTodoRequest; + + // Code to update the todo item in the database + + const todo: UpdateTodoResponse = { + id: todoId, + title: title, + description: description, + createdAt: 'todo_created_at', + updatedAt: 'todo_updated_at', + }; + + return { + statusCode: 200, + body: JSON.stringify(todo), + }; + } catch (error) { + return createErrorResponse(error); + } + } + + async deleteTodo( + event: APIGatewayProxyEvent + ): Promise { + try { + const todoId = event.pathParameters?.id || ''; + + // Code to delete the todo item from the database + + return { + statusCode: 204, + body: '', + }; + } catch (error) { + return createErrorResponse(error); + } + } +} \ No newline at end of file diff --git a/_examples/aws_cdk/dist/src/dynamoDB/todoTable.ts b/_examples/aws_cdk/dist/src/dynamoDB/todoTable.ts new file mode 100644 index 0000000..ea2b4be --- /dev/null +++ b/_examples/aws_cdk/dist/src/dynamoDB/todoTable.ts @@ -0,0 +1,34 @@ +import * as cdk from 'aws-cdk-lib'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; + +export class TodoTable extends cdk.Construct { + constructor(scope: cdk.Construct, id: string, tableName: string) { + super(scope, id); + + const table = new dynamodb.Table(this, 'TodoTable', { + tableName: tableName, + partitionKey: { + name: 'id', + type: dynamodb.AttributeType.STRING, + }, + }); + + table.addGlobalSecondaryIndex({ + indexName: 'CreatedAtIndex', + partitionKey: { + name: 'createdAt', + type: dynamodb.AttributeType.STRING, + }, + projectionType: dynamodb.ProjectionType.ALL, + }); + + table.addGlobalSecondaryIndex({ + indexName: 'UpdatedAtIndex', + partitionKey: { + name: 'updatedAt', + type: dynamodb.AttributeType.STRING, + }, + projectionType: dynamodb.ProjectionType.ALL, + }); + } +} \ No newline at end of file diff --git a/_examples/aws_cdk/dist/src/lambda/todoHandler.ts b/_examples/aws_cdk/dist/src/lambda/todoHandler.ts new file mode 100644 index 0000000..9e804f5 --- /dev/null +++ b/_examples/aws_cdk/dist/src/lambda/todoHandler.ts @@ -0,0 +1,68 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; +import * as AWS from 'aws-sdk'; + +const dynamoDB = new AWS.DynamoDB.DocumentClient(); +const tableName = process.env.DYNAMODB_TABLE_NAME; + +export const createTodoHandler = async ( + event: APIGatewayProxyEvent +): Promise => { + try { + const requestBody = JSON.parse(event.body); + const { title, description } = requestBody; + + // Generate unique ID for the todo item + const id = generateUniqueId(); + + // Get current timestamp + const timestamp = new Date().toISOString(); + + // Create a new todo item + const params: AWS.DynamoDB.DocumentClient.PutItemInput = { + TableName: tableName, + Item: { + id, + title, + description, + createdAt: timestamp, + updatedAt: timestamp + } + }; + + // Save the todo item to DynamoDB + await dynamoDB.put(params).promise(); + + // Return the created todo item + return { + statusCode: 201, + body: JSON.stringify({ + id, + title, + description, + createdAt: timestamp, + updatedAt: timestamp + }) + }; + } catch (error) { + // Handle error and return error response + return createErrorResponse(500, 'Failed to create the todo item.'); + } +}; + +const generateUniqueId = (): string => { + return Date.now().toString(36) + Math.random().toString(36).substr(2, 5); +}; + +const createErrorResponse = ( + statusCode: number, + message: string +): APIGatewayProxyResult => { + return { + statusCode, + body: JSON.stringify({ + error: message + }) + }; +}; + +// Export additional CRUD functions and exports used by other files as well \ No newline at end of file diff --git a/_examples/aws_cdk/dist/tsconfig.json b/_examples/aws_cdk/dist/tsconfig.json new file mode 100644 index 0000000..9824d28 --- /dev/null +++ b/_examples/aws_cdk/dist/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "lib": ["es2018"], + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "outDir": "dist" + }, + "include": ["src"] +} \ No newline at end of file diff --git a/_examples/aws_cdk/prompt.md b/_examples/aws_cdk/prompt.md new file mode 100644 index 0000000..c4bcfae --- /dev/null +++ b/_examples/aws_cdk/prompt.md @@ -0,0 +1,119 @@ +# AWS CDK Todo App Specification + +## Overview + +This specification outlines the requirements for an AWS CDK (Cloud Development Kit) app that implements a CRUD (Create, Read, Update, Delete) API for managing todo items. The app will consist of the following components: + +1. API Gateway: Exposes HTTP endpoints to interact with the backend Lambda functions. +2. Lambda Backend: Responsible for handling API requests and interacting with the DynamoDB. +3. DynamoDB: A managed NoSQL database used to store todo items. + +The project will be managed using `projen`, a project generator tool that simplifies the setup and configuration of AWS CDK projects. + +## Requirements + +The app should satisfy the following requirements: + +1. API Gateway: + - Expose HTTP endpoints for CRUD operations on todo items. + - Use AWS Lambda as the integration for API endpoints. + - Utilize API Gateway REST API. + - Implement proper error handling and validation. + +2. Lambda Backend: + - Implement Lambda functions to handle CRUD operations for todo items. + - Ensure secure access to DynamoDB tables. + - Handle proper error responses and validations. + - Use environment variables to configure the DynamoDB table name and other settings. + +3. DynamoDB: + - Create a DynamoDB table to store todo items. + - The table should have a primary key to uniquely identify each todo item. + +4. Projen: + - Use `projen` to manage the AWS CDK project and its dependencies. + - Configure common project settings such as TypeScript, Jest, and AWS CDK. + +## API Endpoints + +1. Create a Todo Item + + Endpoint: `POST /todos` + + Request Body: + { + "title": "string", + "description": "string" + } + + Response: + Status: 201 Created + Body: + { + "id": "string", + "title": "string", + "description": "string", + "createdAt": "string", + "updatedAt": "string" + } + +2. Get All Todo Items + + Endpoint: `GET /todos` + + Response: + Status: 200 OK + Body: + [ + { + "id": "string", + "title": "string", + "description": "string", + "createdAt": "string", + "updatedAt": "string" + }, + ... + ] + +3. Get a Todo Item by ID + + Endpoint: `GET /todos/{id}` + + Response: + Status: 200 OK + Body: + { + "id": "string", + "title": "string", + "description": "string", + "createdAt": "string", + "updatedAt": "string" + } + +4. Update a Todo Item + + Endpoint: `PUT /todos/{id}` + + Request Body: + { + "title": "string", + "description": "string" + } + + Response: + Status: 200 OK + Body: + { + "id": "string", + "title": "string", + "description": "string", + "createdAt": "string", + "updatedAt": "string" + } + +5. Delete a Todo Item + + Endpoint: `DELETE /todos/{id}` + + Response: + Status: 204 No Content diff --git a/_examples/pong/dist/index.html b/_examples/pong/dist/index.html new file mode 100644 index 0000000..274b7b5 --- /dev/null +++ b/_examples/pong/dist/index.html @@ -0,0 +1,21 @@ + + + + + + Pong Game + + + +
+
+
+
+
+
+ 0 - 0 +
+
+ + + \ No newline at end of file diff --git a/_examples/pong/dist/pong.css b/_examples/pong/dist/pong.css new file mode 100644 index 0000000..f5830b1 --- /dev/null +++ b/_examples/pong/dist/pong.css @@ -0,0 +1,72 @@ +/* pong.css */ + +/* Set the size and position of the playing field */ +#playingField { + width: 600px; + height: 400px; + background-color: #000; + position: relative; + margin: 0 auto; +} + +/* Style the user's paddle */ +#userPaddle { + width: 10px; + height: 60px; + background-color: #fff; + position: absolute; + top: 50%; + transform: translateY(-50%); + left: 10px; +} + +/* Style the computer's paddle */ +#computerPaddle { + width: 10px; + height: 60px; + background-color: #fff; + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 10px; +} + +/* Style the ball */ +#ball { + width: 10px; + height: 10px; + background-color: #fff; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 50%; +} + +/* Style the score counter */ +#userScore, +#computerScore { + color: #fff; + font-size: 24px; + position: absolute; + top: 10px; +} + +#userScore { + left: 10px; +} + +#computerScore { + right: 10px; +} + +/* Style the message */ +#message { + color: #fff; + font-size: 36px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: none; +} \ No newline at end of file diff --git a/_examples/pong/dist/pong.js b/_examples/pong/dist/pong.js new file mode 100644 index 0000000..2b8d1d2 --- /dev/null +++ b/_examples/pong/dist/pong.js @@ -0,0 +1,170 @@ +// DOM elements +const playingField = document.getElementById('playingField'); +const userPaddle = document.getElementById('userPaddle'); +const computerPaddle = document.getElementById('computerPaddle'); +const ball = document.getElementById('ball'); +const userScore = document.getElementById('userScore'); +const computerScore = document.getElementById('computerScore'); +const message = document.getElementById('message'); + +// Game variables +let userPaddleY = 0; +let computerPaddleY = 0; +let ballX = 0; +let ballY = 0; +let ballSpeedX = 0; +let ballSpeedY = 0; +let userScoreCount = 0; +let computerScoreCount = 0; +let gameStarted = false; + +// Start the game +function startGame() { + // Initialize game variables + userPaddleY = playingField.offsetHeight / 2 - userPaddle.offsetHeight / 2; + computerPaddleY = playingField.offsetHeight / 2 - computerPaddle.offsetHeight / 2; + ballX = playingField.offsetWidth / 2 - ball.offsetWidth / 2; + ballY = playingField.offsetHeight / 2 - ball.offsetHeight / 2; + ballSpeedX = 3; + ballSpeedY = 3; + userScoreCount = 0; + computerScoreCount = 0; + gameStarted = true; + + // Reset paddle and ball positions + userPaddle.style.top = userPaddleY + 'px'; + computerPaddle.style.top = computerPaddleY + 'px'; + ball.style.left = ballX + 'px'; + ball.style.top = ballY + 'px'; + + // Reset scores and message + userScore.textContent = userScoreCount; + computerScore.textContent = computerScoreCount; + message.textContent = ''; + + // Start game loop + requestAnimationFrame(moveBall); +} + +// Move the user's paddle +function moveUserPaddle(event) { + if (gameStarted) { + switch (event.key) { + case 'ArrowUp': + userPaddleY -= 10; + break; + case 'ArrowDown': + userPaddleY += 10; + break; + } + // Prevent paddle from going out of bounds + if (userPaddleY < 0) { + userPaddleY = 0; + } + if (userPaddleY > playingField.offsetHeight - userPaddle.offsetHeight) { + userPaddleY = playingField.offsetHeight - userPaddle.offsetHeight; + } + userPaddle.style.top = userPaddleY + 'px'; + } +} + +// Update the computer's paddle position to follow the ball +function updateComputerPaddle() { + if (gameStarted) { + const paddleCenter = computerPaddleY + computerPaddle.offsetHeight / 2; + if (paddleCenter < ballY) { + computerPaddleY += 3; + } else { + computerPaddleY -= 3; + } + // Prevent paddle from going out of bounds + if (computerPaddleY < 0) { + computerPaddleY = 0; + } + if (computerPaddleY > playingField.offsetHeight - computerPaddle.offsetHeight) { + computerPaddleY = playingField.offsetHeight - computerPaddle.offsetHeight; + } + computerPaddle.style.top = computerPaddleY + 'px'; + } +} + +// Move the ball +function moveBall() { + if (gameStarted) { + // Update ball position + ballX += ballSpeedX; + ballY += ballSpeedY; + ball.style.left = ballX + 'px'; + ball.style.top = ballY + 'px'; + + // Check collision with paddles + checkCollision(); + + // Check collision with walls + if (ballY < 0 || ballY > playingField.offsetHeight - ball.offsetHeight) { + ballSpeedY *= -1; + } + + // Check if ball is out of bounds + if (ballX < 0) { + // Computer scores + computerScoreCount++; + computerScore.textContent = computerScoreCount; + resetBall(); + } + if (ballX > playingField.offsetWidth - ball.offsetWidth) { + // User scores + userScoreCount++; + userScore.textContent = userScoreCount; + resetBall(); + } + + // Check win condition + checkWin(); + + // Continue game loop + requestAnimationFrame(moveBall); + } +} + +// Check collision with paddles +function checkCollision() { + if (ballX < userPaddle.offsetWidth && ballY + ball.offsetHeight > userPaddleY && ballY < userPaddleY + userPaddle.offsetHeight) { + // Ball collides with user's paddle + ballSpeedX *= -1; + } + if (ballX + ball.offsetWidth > playingField.offsetWidth - computerPaddle.offsetWidth && ballY + ball.offsetHeight > computerPaddleY && ballY < computerPaddleY + computerPaddle.offsetHeight) { + // Ball collides with computer's paddle + ballSpeedX *= -1; + } +} + +// Reset ball position +function resetBall() { + ballX = playingField.offsetWidth / 2 - ball.offsetWidth / 2; + ballY = playingField.offsetHeight / 2 - ball.offsetHeight / 2; + ballSpeedX *= -1; + ballSpeedY = Math.random() > 0.5 ? 3 : -3; + ball.style.left = ballX + 'px'; + ball.style.top = ballY + 'px'; +} + +// Check win condition +function checkWin() { + if (userScoreCount === 10) { + // User wins + gameStarted = false; + message.textContent = 'You win!'; + } + if (computerScoreCount === 10) { + // Computer wins + gameStarted = false; + message.textContent = 'Computer wins!'; + } +} + +// Event listeners +document.addEventListener('keydown', moveUserPaddle); + +// Start the game +startGame(); diff --git a/_examples/pong/prompt.md b/_examples/pong/prompt.md new file mode 100644 index 0000000..2b7eb8e --- /dev/null +++ b/_examples/pong/prompt.md @@ -0,0 +1,67 @@ +# Pong Browser Game Specification + +## Overview + +This specification outlines the requirements for creating a Pong browser game. The game will be implemented using HTML, CSS, and JavaScript and should run in a Chrome browser. The game will have two paddles: one controlled by the user through arrow keys and the other controlled by the computer. + +## Gameplay + +The Pong game will have the following rules: + +1. The game will be played on a rectangular playing field with a fixed size. +2. There will be two paddles: one for the user and one for the computer. +3. The paddles will be positioned on the left and right edges of the playing field. +4. The user-controlled paddle will be moved up and down using the arrow keys. +5. The computer-controlled paddle will automatically move up and down to follow the ball's position. +6. The game will have a ball that moves across the playing field, bouncing off the walls and paddles. +7. When the ball collides with the user's paddle, it will change its direction, simulating a bounce. +8. When the ball collides with the computer's paddle, it will change its direction, simulating a bounce. +9. The game will have a score counter to keep track of the user's score and the computer's score. +10. The game will end when either the user or the computer reaches a specific score limit (e.g., 10 points). +11. When the game ends, a message will be displayed indicating the winner. + +## User Interface + +The user interface will be designed using HTML and CSS. The following elements should be included: + +1. A rectangular playing field to display the game. +2. Two vertical paddles, one on the left and one on the right, representing the user's paddle and the computer's paddle, respectively. +3. A ball that moves across the playing field. +4. A score counter for the user and the computer. +5. A message to display the winner when the game ends. + +## Controls + +The user will control their paddle using the following keyboard controls: + +- Up Arrow: Move the user's paddle upward. +- Down Arrow: Move the user's paddle downward. + +## Styling + +All styling of the game should be done using CSS. The design should be simple and responsive, ensuring that the game looks good on different screen sizes and orientations. + +## Requirements + +The Pong game should work efficiently in the Google Chrome browser and be compatible with modern web standards. The game should not use any images; all visual elements should be created using HTML and CSS. + +## Implementation + +The Pong game will be implemented using HTML for the structure, CSS for styling, and JavaScript for game logic and interactivity. + +The game should be organized into separate files for HTML, CSS, and JavaScript. For example: + +index.html # HTML file for the game structure +pong.css # CSS file for styling the game +pong.js # JavaScript file for game logic and interactivity + +## Additional Features (Optional) + +Optionally, the following features can be added to enhance the game: + +1. Sound Effects: Add sound effects for paddle-ball collisions and scoring points. +2. Difficulty Levels: Allow the user to choose from different difficulty levels for the computer-controlled paddle. +3. Mobile Support: Implement touch controls for mobile devices to allow users to play the game on touchscreens. +4. Pause/Restart: Add options to pause and restart the game during gameplay. + +These additional features can be considered after implementing the core functionality of the Pong browser game. diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..13dfe02 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,198 @@ +// Package cmd provides a command-line interface for the Genie code generation tool. +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/hupe1980/genie/pkg/codegen" + "github.com/spf13/cobra" + "golang.org/x/sync/errgroup" +) + +// Execute runs the root command and handles any errors that may occur. +func Execute(version string) { + printLogo() + + rootCmd := newRootCmd(version) + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +// globalOptions holds the command-line flags for Genie. +type globalOptions struct { + apiKey string + prompt string + model string + temperature float32 + outdir string +} + +// newRootCmd creates the root Cobra command for Genie. +func newRootCmd(version string) *cobra.Command { + globalOpts := &globalOptions{} + + cmd := &cobra.Command{ + Use: "genie", + Version: version, + Short: "Genie is a Proof of Concept (POC) source code generator that showcases the potential of utilizing Large Language Models (LLMs) for code generation.", + Example: `genie -p "Create a python hello world" +genie -p prompt.txt`, + SilenceErrors: true, + RunE: func(cmd *cobra.Command, args []string) error { + apiKey := globalOpts.apiKey + if apiKey == "" { + apiKey = os.Getenv("OPENAI_API_KEY") + } + + cg, err := codegen.New(apiKey, func(o *codegen.CodeGenOptions) { + o.ModelName = globalOpts.model + }) + if err != nil { + return err + } + + prompt, err := readFileOrString(globalOpts.prompt) + if err != nil { + return err + } + + fmt.Println("Create list of files:") + + fpOutput, err := cg.FilePaths(&codegen.FilePathsInput{ + Prompt: prompt, + }) + if err != nil { + return err + } + + printListWithBullets(fpOutput.FilePaths) + fmt.Println() + + fmt.Println("Reasoning:") + printListWithBullets(fpOutput.Reasoning) + fmt.Println() + + fmt.Println("Create list of shared Dependecies:") + + sdOutput, err := cg.SharedDependencies(&codegen.SharedDependenciesInput{ + Prompt: prompt, + FilePaths: fpOutput.FilePaths, + }) + if err != nil { + return err + } + + printListWithBullets(sdOutput.SharedDependencies) + fmt.Println() + + fmt.Println("Reasoning:") + printListWithBullets(sdOutput.Reasoning) + fmt.Println() + + g := new(errgroup.Group) + g.SetLimit(3) + + for _, fp := range fpOutput.FilePaths { + fp := fp + + filePath := filepath.Join(globalOpts.outdir, fp) + + // Check if file already exists: + if _, statErr := os.Stat(filePath); statErr == nil { + fmt.Printf("File %v already exists, skipping\n", filePath) + continue + } + + g.Go(func() error { + cgOutput, genErr := cg.GenerateSourceCode(&codegen.GenerateSourceCodeInput{ + Prompt: prompt, + Filename: fp, + FilePaths: fpOutput.FilePaths, + SharedDependencies: sdOutput.SharedDependencies, + }) + if genErr != nil { + return genErr + } + + // Create the folder if it doesn't exist + folder := filepath.Dir(filePath) + + if _, statErr := os.Stat(folder); os.IsNotExist(statErr) { + if mkErr := os.MkdirAll(folder, 0755); mkErr != nil { + return mkErr + } + } + + // Write the data to the file. + if wErr := os.WriteFile(filePath, []byte(cgOutput.Source), 0600); wErr != nil { + return wErr + } + + fmt.Printf("File %s created\n", fp) + + return nil + }) + } + + err = g.Wait() + if err != nil { + return err + } + + fmt.Println() + fmt.Println(cg.Info()) + + return nil + }, + } + + cmd.PersistentFlags().StringVarP(&globalOpts.apiKey, "api-key", "", "", "openAI api key") + cmd.PersistentFlags().StringVarP(&globalOpts.prompt, "prompt", "p", "", "prompt to use (required)") + cmd.PersistentFlags().StringVarP(&globalOpts.model, "model", "m", codegen.DefaultModelName, "model to use") + cmd.PersistentFlags().StringVarP(&globalOpts.outdir, "outdir", "o", "dist", "outdir to use") + cmd.PersistentFlags().Float32VarP(&globalOpts.temperature, "temperature", "t", codegen.DefaultTemperature, "temperature to use") + + _ = cmd.MarkPersistentFlagRequired("prompt") + + return cmd +} + +// readFileOrString reads the contents of a file if the input is a valid filename, +// otherwise, it returns the input string as it is. +func readFileOrString(input string) (string, error) { + if fileInfo, err := os.Stat(input); err == nil && !fileInfo.IsDir() { + // Assuming it's a valid file path + content, err := os.ReadFile(input) + if err != nil { + return "", err + } + + return string(content), nil + } + + return input, nil +} + +// printListWithBullets prints a slice with bullet points. +func printListWithBullets[T any](slice []T) { + for _, item := range slice { + fmt.Printf("• %v\n", item) + } +} + +// printLogo prints the Genie logo. +func printLogo() { + logo := ` ██████ ███████ ███ ██ ██ ███████ +██ ██ ████ ██ ██ ██ +██ ███ █████ ██ ██ ██ ██ █████ +██ ██ ██ ██ ██ ██ ██ ██ + ██████ ███████ ██ ████ ██ ███████` + + fmt.Println() + fmt.Println(logo) + fmt.Println() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..99d2247 --- /dev/null +++ b/go.mod @@ -0,0 +1,47 @@ +module github.com/hupe1980/genie + +go 1.20 + +require github.com/hupe1980/golc v0.0.46 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) + +require ( + cloud.google.com/go/ai v0.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/PuerkitoBio/goquery v1.8.1 // indirect + github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/cohere-ai/tokenizer v1.1.2 // indirect + github.com/dlclark/regexp2 v1.10.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/gax-go/v2 v2.12.0 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/hupe1980/go-promptlayer v0.0.6 // indirect + github.com/hupe1980/go-tiktoken v0.0.4 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sashabaranov/go-openai v1.13.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/cobra v1.7.0 + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/net v0.12.0 // indirect + golang.org/x/sync v0.3.0 + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect + google.golang.org/api v0.130.0 // indirect + google.golang.org/genproto v0.0.0-20230710151506-e685fd7b542b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230710151506-e685fd7b542b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230710151506-e685fd7b542b // indirect + google.golang.org/grpc v1.56.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d823e89 --- /dev/null +++ b/go.sum @@ -0,0 +1,153 @@ +cloud.google.com/go/ai v0.1.0 h1:MBLOQ4Ac3AwgHTd689r8ZwlPfpnimcjn2fJvxHzXXGA= +cloud.google.com/go/ai v0.1.0/go.mod h1:Lnyc4wn/tozQm4m1kdEFgQFooBuboYCafm5OzqOuCGU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/cohere-ai/tokenizer v1.1.2 h1:t3KwUBSpKiBVFtpnHBfVIQNmjfZUuqFVYuSFkZYOWpU= +github.com/cohere-ai/tokenizer v1.1.2/go.mod h1:9MNFPd9j1fuiEK3ua2HSCUxxcrfGMlSqpa93livg/C0= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hupe1980/go-promptlayer v0.0.6 h1:cga58zaQYPz7wo7EZG1a0goBj7OzoE5s3HT2Dl1Wp6g= +github.com/hupe1980/go-promptlayer v0.0.6/go.mod h1:tiCI1t6OkHe9Qio95G5+DMPOONboRJ67sSn1F0afqcU= +github.com/hupe1980/go-tiktoken v0.0.4 h1:dHyK0lki7rt1BTSKloaje/HA9aOadsN/gDjyfhAKZro= +github.com/hupe1980/go-tiktoken v0.0.4/go.mod h1:RMy/MYYSG9zEAv7tvjbdMgsozwbjaXLR6sKd8xfa4IY= +github.com/hupe1980/golc v0.0.46 h1:AHfe6/5Q1R9ArKO0Dez1oCgh5I6rYUJVc/HLxWWhGnk= +github.com/hupe1980/golc v0.0.46/go.mod h1:ENLMm2dVbK3TcdzOIydknfwM4ecli5M1ZPQ2fkkD0Oo= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sashabaranov/go-openai v1.13.0 h1:EAusFfnhaMaaUspUZ2+MbB/ZcVeD4epJmTOlZ+8AcAE= +github.com/sashabaranov/go-openai v1.13.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.130.0 h1:A50ujooa1h9iizvfzA4rrJr2B7uRmWexwbekQ2+5FPQ= +google.golang.org/api v0.130.0/go.mod h1:J/LCJMYSDFvAVREGCbrESb53n4++NMBDetSHGL5I5RY= +google.golang.org/genproto v0.0.0-20230710151506-e685fd7b542b h1:VCRiEG1fCSUI/+vnvyFY2zSxo5BZ2FcYENNR8afEQr0= +google.golang.org/genproto v0.0.0-20230710151506-e685fd7b542b/go.mod h1:NV95I5n2G0tFsBwqd9ZJ5tKNnNJgwCkz61wEZsHTB/c= +google.golang.org/genproto/googleapis/api v0.0.0-20230710151506-e685fd7b542b h1:3TOJqtuBSNLfLXJ2rZrzKFPz/WqgEjXaEYy/mZ8/j1k= +google.golang.org/genproto/googleapis/api v0.0.0-20230710151506-e685fd7b542b/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230710151506-e685fd7b542b h1:BC7Q0uXfp6VFXnNWp5RqATIN/viqCGkqBO8+HxzH/jY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230710151506-e685fd7b542b/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI= +google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..075e2e7 --- /dev/null +++ b/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "github.com/hupe1980/genie/cmd" +) + +var ( + version = "dev" +) + +func main() { + cmd.Execute(version) +} diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go new file mode 100644 index 0000000..b11244c --- /dev/null +++ b/pkg/codegen/codegen.go @@ -0,0 +1,224 @@ +package codegen + +import ( + "context" + _ "embed" + "encoding/json" + "fmt" + "regexp" + + "github.com/hupe1980/golc/callback" + "github.com/hupe1980/golc/model" + "github.com/hupe1980/golc/model/chatmodel" + "github.com/hupe1980/golc/prompt" + "github.com/hupe1980/golc/schema" + "gopkg.in/yaml.v2" +) + +const ( + // DefaultModelName is the default model name used for code generation. + DefaultModelName = "gpt-3.5-turbo" + + // DefaultTemperature is the default temperature used for code generation. + DefaultTemperature = 0.4 +) + +//go:embed prompts/file_paths_system_prompt.tpl +var filePathsSystemPrompt string + +//go:embed prompts/file_paths_human_prompt.tpl +var filePathsHumanPrompt string + +//go:embed prompts/shared_dependencies_system_prompt.tpl +var sharedDependenciesSystemPrompt string + +//go:embed prompts/code_generation_system_prompt.tpl +var codeGenerationSystemPrompt string + +//go:embed prompts/code_generation_human_prompt.tpl +var codeGenerationHumanPrompt string + +// CodeGenOptions contains options for configuring the CodeGen. +type CodeGenOptions struct { + // ModelName is the name of the model to use for code generation. + ModelName string + + // Temperatur is the temperature setting for text generation. Higher values produce more random output. + Temperature float32 +} + +// CodeGen represents a code generator based on a chat model. +type CodeGen struct { + model schema.ChatModel + info *callback.OpenAIHandler + opts CodeGenOptions +} + +// New creates a new CodeGen instance with the given API key and optional configuration options. +func New(apiKey string, optFns ...func(o *CodeGenOptions)) (*CodeGen, error) { + opts := CodeGenOptions{ + ModelName: DefaultModelName, + Temperature: DefaultTemperature, + } + + for _, fn := range optFns { + fn(&opts) + } + + info := callback.NewOpenAIHandler() + + openAI, err := chatmodel.NewOpenAI(apiKey, func(o *chatmodel.OpenAIOptions) { + o.ModelName = opts.ModelName + o.Temperature = opts.Temperature + o.Callbacks = []schema.Callback{info} + }) + if err != nil { + return nil, err + } + + return &CodeGen{ + model: openAI, + info: info, + opts: opts, + }, nil +} + +// FilePathsInput represents the input for generating file paths. +type FilePathsInput struct { + Prompt string +} + +// FilePathsOutput represents the output for generating file paths. +type FilePathsOutput struct { + Reasoning []string `json:"reasoning"` + FilePaths []string `json:"file_paths"` +} + +// FilePaths generates a list of file paths based on the input prompt. +func (cg *CodeGen) FilePaths(input *FilePathsInput) (*FilePathsOutput, error) { + ct := prompt.NewChatTemplate([]prompt.MessageTemplate{ + prompt.NewSystemMessageTemplate(filePathsSystemPrompt), + prompt.NewHumanMessageTemplate(filePathsHumanPrompt), + }) + + pv, err := ct.FormatPrompt(map[string]any{ + "prompt": input.Prompt, + }) + if err != nil { + return nil, err + } + + result, err := model.GeneratePrompt(context.Background(), cg.model, pv) + if err != nil { + return nil, err + } + + output := FilePathsOutput{} + if err = json.Unmarshal(toJSON(result.Generations[0].Text), &output); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w\nRaw data: %v", err, result.Generations[0].Text) + } + + return &output, nil +} + +// SharedDependenciesInput represents the input for generating shared dependencies. +type SharedDependenciesInput struct { + Prompt string + FilePaths []string +} + +// SharedDependency represents a shared dependency with name, description, and symbols. +type SharedDependency struct { + Name string `json:"name"` + Description string `json:"description"` + Symbols []string `json:"symbols"` +} + +// SharedDependenciesOutput represents the output for generating shared dependencies. +type SharedDependenciesOutput struct { + Reasoning []string `json:"reasoning"` + SharedDependencies []SharedDependency `json:"shared_dependencies"` +} + +// SharedDependencies generates shared dependencies based on the input prompt and file paths. +func (cg *CodeGen) SharedDependencies(input *SharedDependenciesInput) (*SharedDependenciesOutput, error) { + t := prompt.NewSystemMessageTemplate(sharedDependenciesSystemPrompt) + + pv, err := t.FormatPrompt(map[string]any{ + "prompt": input.Prompt, + "filePaths": input.FilePaths, + }) + if err != nil { + return nil, err + } + + result, err := model.GeneratePrompt(context.Background(), cg.model, pv) + if err != nil { + return nil, err + } + + output := SharedDependenciesOutput{} + if err = json.Unmarshal(toJSON(result.Generations[0].Text), &output); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w\nRaw data: %v", err, result.Generations[0].Text) + } + + return &output, nil +} + +// GenerateSourceCodeInput represents the input for generating source code. +type GenerateSourceCodeInput struct { + Prompt string + Filename string + FilePaths []string + SharedDependencies []SharedDependency +} + +// GenerateSourceCodeOutput represents the output for generating source code. +type GenerateSourceCodeOutput struct { + Filename string `json:"filename"` + Source string `json:"source"` +} + +// GenerateSourceCode generates source code based on the input prompt, filename, file paths, and shared dependencies. +func (cg *CodeGen) GenerateSourceCode(input *GenerateSourceCodeInput) (*GenerateSourceCodeOutput, error) { + sharedDepsYaml, err := yaml.Marshal(input.SharedDependencies) + if err != nil { + return nil, fmt.Errorf("failed to marshal shared dependencies: %w", err) + } + + ct := prompt.NewChatTemplate([]prompt.MessageTemplate{ + prompt.NewSystemMessageTemplate(codeGenerationSystemPrompt), + prompt.NewHumanMessageTemplate(codeGenerationHumanPrompt), + }) + + pv, err := ct.FormatPrompt(map[string]any{ + "prompt": input.Prompt, + "filePaths": input.FilePaths, + "shared_dependencies": string(sharedDepsYaml), + "filename": input.Filename, + }) + if err != nil { + return nil, err + } + + result, err := model.GeneratePrompt(context.Background(), cg.model, pv) + if err != nil { + return nil, err + } + + return &GenerateSourceCodeOutput{ + Filename: input.Filename, + Source: result.Generations[0].Text, + }, nil +} + +// Info returns the information about the code generator. +func (cg *CodeGen) Info() string { + return cg.info.String() +} + +// toJSON extracts a JSON string from a given string. +func toJSON(s string) []byte { + re := regexp.MustCompile(`(?s)\{.*\}`) + return re.Find([]byte(s)) +} diff --git a/pkg/codegen/prompts/code_generation_human_prompt.tpl b/pkg/codegen/prompts/code_generation_human_prompt.tpl new file mode 100644 index 0000000..5088db1 --- /dev/null +++ b/pkg/codegen/prompts/code_generation_human_prompt.tpl @@ -0,0 +1,18 @@ +We have broken up the program into per-file generation. +Now your job is to generate only the code for the file {{.filename}}. +Make sure to have consistent filenames if you reference other files we are also generating. + +Remember that you must obey 3 things: + - you are generating code for the file {{.filename}} + - do not stray from the names of the files and the shared dependencies we have decided on + - MOST IMPORTANT OF ALL - the purpose of our app is {{.prompt}} - every line of code you generate must be valid code. Do not include code fences in your response, for example + +Bad response (because it contains the code fence): +```javascript +console.log("hello world") +``` + +Good response (because it only contains the code): +console.log("hello world") + +Begin generating the code now. \ No newline at end of file diff --git a/pkg/codegen/prompts/code_generation_system_prompt.tpl b/pkg/codegen/prompts/code_generation_system_prompt.tpl new file mode 100644 index 0000000..d650f97 --- /dev/null +++ b/pkg/codegen/prompts/code_generation_system_prompt.tpl @@ -0,0 +1,13 @@ +You are an AI developer who is trying to write a program that will generate code for the user based on their intent. +Do not leave any todos, fully implement every feature requested. + +When writing code, add comments to explain what you intend to do and why it aligns with the program plan and specific instructions from the original prompt. + +The app is: {{.prompt}} + +The files we have decided to generate are: {{ toJson .filePaths}} + +The shared dependencies (like filenames and variable names) we have decided on are: {{.shared_dependencies}} + +Only write valid code for the given filepath and file type, and return only the code. +Do not add any other explanation, only return valid code for that file type. \ No newline at end of file diff --git a/pkg/codegen/prompts/file_paths_human_prompt.tpl b/pkg/codegen/prompts/file_paths_human_prompt.tpl new file mode 100644 index 0000000..0bcc67a --- /dev/null +++ b/pkg/codegen/prompts/file_paths_human_prompt.tpl @@ -0,0 +1 @@ +{{.prompt}} \ No newline at end of file diff --git a/pkg/codegen/prompts/file_paths_system_prompt.tpl b/pkg/codegen/prompts/file_paths_system_prompt.tpl new file mode 100644 index 0000000..1286ffc --- /dev/null +++ b/pkg/codegen/prompts/file_paths_system_prompt.tpl @@ -0,0 +1,12 @@ +You are an AI developer who is trying to write a program that will generate code for the user based on their intent. +Do not leave any todos, fully implement every feature requested. + +When writing code, add comments to explain what you intend to do and why it aligns with the program plan and specific instructions from the original prompt. + +When given their intent, create a complete, exhaustive list of file paths that the user would write to make the program. + +Your repsonse must be JSON formatted and contain the following keys: +"reasoning": a list of strings that explain your chain of thought (include 5-10) +"file_paths": a list of strings that are the file paths that the user would write to make the program. + +Do not emit any other output. \ No newline at end of file diff --git a/pkg/codegen/prompts/shared_dependencies_system_prompt.tpl b/pkg/codegen/prompts/shared_dependencies_system_prompt.tpl new file mode 100644 index 0000000..2665de2 --- /dev/null +++ b/pkg/codegen/prompts/shared_dependencies_system_prompt.tpl @@ -0,0 +1,21 @@ +You are an AI developer who is trying to write a program that will generate code for the user based on their intent. +Do not leave any todos, fully implement every feature requested. + +When writing code, add comments to explain what you intend to do and why it aligns with the program plan and specific instructions from the original prompt. + +In response to the user's prompt: + +--- +the app is: {{.prompt}} +--- + +the files we have decided to generate are: {{ toJson .filePaths}} + +Now that we have a list of files, we need to understand what dependencies they share. +Please name and briefly describe what is shared between the files we are generating, including exported variables, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names. + +Your repsonse must be JSON formatted and contain the following keys: +"reasoning": a list of strings that explain your chain of thought (include 5-10) +"shared_dependencies": a the list of shared dependencies, include a symbol name, a description, and the set of symbols or files. use "name", "description", and "symbols" as the keys. + +Do not emit any other output. \ No newline at end of file