Skip to content

Commit 289a347

Browse files
authored
Merge pull request #38 from semanser/docker-release
Release an official Docker image
2 parents c69929c + c05037d commit 289a347

30 files changed

+373
-313
lines changed

.dockerignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
frontend/dist
2+
frontend/node_modules
3+
frontend/dist
4+
frontend/.env.local
5+
6+
backend/.env
7+
8+
**/*.log
9+
**/*.env
10+
**/.DS_Store
11+
**/Thumbs.db

.github/workflows/release.yml

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Release Docker Image
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v[0-9]+.[0-9]+.[0-9]+'
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v4
15+
16+
- name: Set up QEMU
17+
uses: docker/setup-qemu-action@v3
18+
19+
- name: Set up Docker Buildx
20+
uses: docker/setup-buildx-action@v3
21+
22+
- name: Login to GitHub Container Registry
23+
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
24+
25+
- name: Build and push Docker image
26+
uses: docker/build-push-action@v3
27+
with:
28+
context: .
29+
file: ./Dockerfile
30+
platforms: linux/amd64,linux/arm64
31+
push: true
32+
tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
33+
34+
- name: Create GitHub Release
35+
uses: softprops/action-gh-release@v2

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
.DS_Store
22
.env
3+
.env.*
34
.envrc
5+
fe

Dockerfile

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# STEP 1: Build the frontend
2+
FROM node:21-slim as fe-build
3+
4+
ENV NODE_ENV=production
5+
ENV VITE_API_URL=localhost:3000
6+
7+
WORKDIR /frontend
8+
9+
COPY ./backend/graph/schema.graphqls ../backend/graph/
10+
11+
COPY frontend/ .
12+
13+
# --production=false is required because we want to install the @graphql-codegen/cli package (and it's in the devDependencies)
14+
# https://classic.yarnpkg.com/lang/en/docs/cli/install/#toc-yarn-install-production-true-false
15+
RUN yarn install --frozen-lockfile --production=false
16+
RUN ls -la /frontend
17+
RUN yarn build
18+
19+
# STEP 2: Build the backend
20+
FROM golang:1.22-alpine as be-build
21+
ENV CGO_ENABLED=1
22+
RUN apk add --no-cache gcc musl-dev
23+
24+
WORKDIR /backend
25+
26+
COPY backend/ .
27+
28+
RUN go mod download
29+
30+
RUN go build -ldflags='-extldflags "-static"' -o /app
31+
32+
# STEP 3: Build the final image
33+
FROM alpine:3.14
34+
35+
COPY --from=be-build /app /app
36+
COPY --from=fe-build /frontend/dist /fe
37+
38+
# Install sqlite3
39+
40+
RUN apk add --no-cache sqlite
41+
42+
CMD /app

backend/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
ai-coder
22
tmp
3+
database.db
4+
.env.*

backend/agent/agent.go

+19-34
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package agent
22

33
import (
44
"context"
5+
"database/sql"
56
"encoding/json"
67
"fmt"
78
"log"
89

910
"github.com/invopop/jsonschema"
10-
"github.com/jackc/pgx/v5/pgtype"
1111
openai "github.com/sashabaranov/go-openai"
1212
"github.com/semanser/ai-coder/assets"
1313
"github.com/semanser/ai-coder/config"
@@ -137,7 +137,7 @@ func NextTask(args AgentPrompt) *database.Task {
137137
if task.Type.String == "input" {
138138
messages = append(messages, openai.ChatCompletionMessage{
139139
Role: openai.ChatMessageRoleUser,
140-
Content: string(task.Args),
140+
Content: task.Args.String,
141141
})
142142
}
143143

@@ -149,7 +149,7 @@ func NextTask(args AgentPrompt) *database.Task {
149149
ID: task.ToolCallID.String,
150150
Function: openai.FunctionCall{
151151
Name: task.Type.String,
152-
Arguments: string(task.Args),
152+
Arguments: task.Args.String,
153153
},
154154
Type: openai.ToolTypeFunction,
155155
},
@@ -212,7 +212,7 @@ func NextTask(args AgentPrompt) *database.Task {
212212
}
213213

214214
task := database.Task{
215-
Type: database.StringToPgText(tool.Function.Name),
215+
Type: database.StringToNullString(tool.Function.Name),
216216
}
217217

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

232232
// Sometimes the model returns an empty string for the message
233233
msg := string(params.Message)
234234
if msg == "" {
235235
msg = params.Input
236236
}
237237

238-
task.Message = database.StringToPgText(msg)
239-
task.Status = database.StringToPgText("in_progress")
238+
task.Message = database.StringToNullString(msg)
239+
task.Status = database.StringToNullString("in_progress")
240240

241241
case "browser":
242242
params, err := extractArgs(tool.Function.Arguments, &BrowserArgs{})
@@ -249,11 +249,8 @@ func NextTask(args AgentPrompt) *database.Task {
249249
log.Printf("Failed to marshal browser args, asking user: %v", err)
250250
return defaultAskTask("There was an error opening the browser")
251251
}
252-
task.Args = args
253-
task.Message = pgtype.Text{
254-
String: string(params.Message),
255-
Valid: true,
256-
}
252+
task.Args = database.StringToNullString(string(args))
253+
task.Message = database.StringToNullString(string(params.Message))
257254
case "code":
258255
params, err := extractArgs(tool.Function.Arguments, &CodeArgs{})
259256
if err != nil {
@@ -265,11 +262,8 @@ func NextTask(args AgentPrompt) *database.Task {
265262
log.Printf("Failed to marshal code args, asking user: %v", err)
266263
return defaultAskTask("There was an error reading or updating the file")
267264
}
268-
task.Args = args
269-
task.Message = pgtype.Text{
270-
String: string(params.Message),
271-
Valid: true,
272-
}
265+
task.Args = database.StringToNullString(string(args))
266+
task.Message = database.StringToNullString(string(params.Message))
273267
case "ask":
274268
params, err := extractArgs(tool.Function.Arguments, &AskArgs{})
275269
if err != nil {
@@ -281,11 +275,8 @@ func NextTask(args AgentPrompt) *database.Task {
281275
log.Printf("Failed to marshal ask args, asking user: %v", err)
282276
return defaultAskTask("There was an error asking the user for additional information")
283277
}
284-
task.Args = args
285-
task.Message = pgtype.Text{
286-
String: string(params.Message),
287-
Valid: true,
288-
}
278+
task.Args = database.StringToNullString(string(args))
279+
task.Message = database.StringToNullString(string(params.Message))
289280
case "done":
290281
params, err := extractArgs(tool.Function.Arguments, &DoneArgs{})
291282
if err != nil {
@@ -296,28 +287,22 @@ func NextTask(args AgentPrompt) *database.Task {
296287
if err != nil {
297288
return defaultAskTask("There was an error marking the task as done")
298289
}
299-
task.Args = args
300-
task.Message = pgtype.Text{
301-
String: string(params.Message),
302-
Valid: true,
303-
}
290+
task.Args = database.StringToNullString(string(args))
291+
task.Message = database.StringToNullString(string(params.Message))
304292
}
305293

306-
task.ToolCallID = pgtype.Text{
307-
String: tool.ID,
308-
Valid: true,
309-
}
294+
task.ToolCallID = database.StringToNullString(tool.ID)
310295

311296
return &task
312297
}
313298

314299
func defaultAskTask(message string) *database.Task {
315300
task := database.Task{
316-
Type: database.StringToPgText("ask"),
301+
Type: database.StringToNullString("ask"),
317302
}
318303

319-
task.Args = []byte("{}")
320-
task.Message = pgtype.Text{
304+
task.Args = database.StringToNullString("{}")
305+
task.Message = sql.NullString{
321306
String: fmt.Sprintf("%s. What should I do next?", message),
322307
Valid: true,
323308
}

backend/database/containers.sql.go

+18-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/database/database.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package database
22

3-
import "github.com/jackc/pgx/v5/pgtype"
3+
import (
4+
"database/sql"
5+
)
46

5-
func StringToPgText(s string) pgtype.Text {
6-
return pgtype.Text{String: s, Valid: true}
7+
func StringToNullString(s string) sql.NullString {
8+
return sql.NullString{String: s, Valid: true}
79
}

backend/database/db.go

+6-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)