Skip to content

Commit

Permalink
Merge pull request #38 from semanser/docker-release
Browse files Browse the repository at this point in the history
Release an official Docker image
  • Loading branch information
semanser committed Apr 1, 2024
2 parents c69929c + c05037d commit 289a347
Show file tree
Hide file tree
Showing 30 changed files with 373 additions and 313 deletions.
11 changes: 11 additions & 0 deletions .dockerignore
@@ -0,0 +1,11 @@
frontend/dist
frontend/node_modules
frontend/dist
frontend/.env.local

backend/.env

**/*.log
**/*.env
**/.DS_Store
**/Thumbs.db
35 changes: 35 additions & 0 deletions .github/workflows/release.yml
@@ -0,0 +1,35 @@
name: Release Docker Image

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GitHub Container Registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Build and push Docker image
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
2 changes: 2 additions & 0 deletions .gitignore
@@ -1,3 +1,5 @@
.DS_Store
.env
.env.*
.envrc
fe
42 changes: 42 additions & 0 deletions Dockerfile
@@ -0,0 +1,42 @@
# STEP 1: Build the frontend
FROM node:21-slim as fe-build

ENV NODE_ENV=production
ENV VITE_API_URL=localhost:3000

WORKDIR /frontend

COPY ./backend/graph/schema.graphqls ../backend/graph/

COPY frontend/ .

# --production=false is required because we want to install the @graphql-codegen/cli package (and it's in the devDependencies)
# https://classic.yarnpkg.com/lang/en/docs/cli/install/#toc-yarn-install-production-true-false
RUN yarn install --frozen-lockfile --production=false
RUN ls -la /frontend
RUN yarn build

# STEP 2: Build the backend
FROM golang:1.22-alpine as be-build
ENV CGO_ENABLED=1
RUN apk add --no-cache gcc musl-dev

WORKDIR /backend

COPY backend/ .

RUN go mod download

RUN go build -ldflags='-extldflags "-static"' -o /app

# STEP 3: Build the final image
FROM alpine:3.14

COPY --from=be-build /app /app
COPY --from=fe-build /frontend/dist /fe

# Install sqlite3

RUN apk add --no-cache sqlite

CMD /app
2 changes: 2 additions & 0 deletions backend/.gitignore
@@ -1,2 +1,4 @@
ai-coder
tmp
database.db
.env.*
53 changes: 19 additions & 34 deletions backend/agent/agent.go
Expand Up @@ -2,12 +2,12 @@ package agent

import (
"context"
"database/sql"
"encoding/json"
"fmt"
"log"

"github.com/invopop/jsonschema"
"github.com/jackc/pgx/v5/pgtype"
openai "github.com/sashabaranov/go-openai"
"github.com/semanser/ai-coder/assets"
"github.com/semanser/ai-coder/config"
Expand Down Expand Up @@ -137,7 +137,7 @@ func NextTask(args AgentPrompt) *database.Task {
if task.Type.String == "input" {
messages = append(messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleUser,
Content: string(task.Args),
Content: task.Args.String,
})
}

Expand All @@ -149,7 +149,7 @@ func NextTask(args AgentPrompt) *database.Task {
ID: task.ToolCallID.String,
Function: openai.FunctionCall{
Name: task.Type.String,
Arguments: string(task.Args),
Arguments: task.Args.String,
},
Type: openai.ToolTypeFunction,
},
Expand Down Expand Up @@ -212,7 +212,7 @@ func NextTask(args AgentPrompt) *database.Task {
}

task := database.Task{
Type: database.StringToPgText(tool.Function.Name),
Type: database.StringToNullString(tool.Function.Name),
}

switch tool.Function.Name {
Expand All @@ -227,16 +227,16 @@ func NextTask(args AgentPrompt) *database.Task {
log.Printf("Failed to marshal terminal args, asking user: %v", err)
return defaultAskTask("There was an error running the terminal command")
}
task.Args = args
task.Args = database.StringToNullString(string(args))

// Sometimes the model returns an empty string for the message
msg := string(params.Message)
if msg == "" {
msg = params.Input
}

task.Message = database.StringToPgText(msg)
task.Status = database.StringToPgText("in_progress")
task.Message = database.StringToNullString(msg)
task.Status = database.StringToNullString("in_progress")

case "browser":
params, err := extractArgs(tool.Function.Arguments, &BrowserArgs{})
Expand All @@ -249,11 +249,8 @@ func NextTask(args AgentPrompt) *database.Task {
log.Printf("Failed to marshal browser args, asking user: %v", err)
return defaultAskTask("There was an error opening the browser")
}
task.Args = args
task.Message = pgtype.Text{
String: string(params.Message),
Valid: true,
}
task.Args = database.StringToNullString(string(args))
task.Message = database.StringToNullString(string(params.Message))
case "code":
params, err := extractArgs(tool.Function.Arguments, &CodeArgs{})
if err != nil {
Expand All @@ -265,11 +262,8 @@ func NextTask(args AgentPrompt) *database.Task {
log.Printf("Failed to marshal code args, asking user: %v", err)
return defaultAskTask("There was an error reading or updating the file")
}
task.Args = args
task.Message = pgtype.Text{
String: string(params.Message),
Valid: true,
}
task.Args = database.StringToNullString(string(args))
task.Message = database.StringToNullString(string(params.Message))
case "ask":
params, err := extractArgs(tool.Function.Arguments, &AskArgs{})
if err != nil {
Expand All @@ -281,11 +275,8 @@ func NextTask(args AgentPrompt) *database.Task {
log.Printf("Failed to marshal ask args, asking user: %v", err)
return defaultAskTask("There was an error asking the user for additional information")
}
task.Args = args
task.Message = pgtype.Text{
String: string(params.Message),
Valid: true,
}
task.Args = database.StringToNullString(string(args))
task.Message = database.StringToNullString(string(params.Message))
case "done":
params, err := extractArgs(tool.Function.Arguments, &DoneArgs{})
if err != nil {
Expand All @@ -296,28 +287,22 @@ func NextTask(args AgentPrompt) *database.Task {
if err != nil {
return defaultAskTask("There was an error marking the task as done")
}
task.Args = args
task.Message = pgtype.Text{
String: string(params.Message),
Valid: true,
}
task.Args = database.StringToNullString(string(args))
task.Message = database.StringToNullString(string(params.Message))
}

task.ToolCallID = pgtype.Text{
String: tool.ID,
Valid: true,
}
task.ToolCallID = database.StringToNullString(tool.ID)

return &task
}

func defaultAskTask(message string) *database.Task {
task := database.Task{
Type: database.StringToPgText("ask"),
Type: database.StringToNullString("ask"),
}

task.Args = []byte("{}")
task.Message = pgtype.Text{
task.Args = database.StringToNullString("{}")
task.Message = sql.NullString{
String: fmt.Sprintf("%s. What should I do next?", message),
Valid: true,
}
Expand Down
34 changes: 18 additions & 16 deletions backend/database/containers.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions backend/database/database.go
@@ -1,7 +1,9 @@
package database

import "github.com/jackc/pgx/v5/pgtype"
import (
"database/sql"
)

func StringToPgText(s string) pgtype.Text {
return pgtype.Text{String: s, Valid: true}
func StringToNullString(s string) sql.NullString {
return sql.NullString{String: s, Valid: true}
}
13 changes: 6 additions & 7 deletions backend/database/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 289a347

Please sign in to comment.