aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/config.go (renamed from config.go)11
-rw-r--r--generators/gemini/gemini.go67
-rw-r--r--generators/prompt.go80
-rw-r--r--go.mod32
-rw-r--r--go.sum85
-rw-r--r--main.go22
-rw-r--r--routers/router.go53
-rw-r--r--types/types.go (renamed from types.go)11
8 files changed, 336 insertions, 25 deletions
diff --git a/config.go b/config/config.go
index 8629489..527b6cc 100644
--- a/config.go
+++ b/config/config.go
@@ -1,21 +1,22 @@
-package main
+package config
import (
"log"
"os"
"strconv"
+ "thunderbird-ai-compose-server/types"
"github.com/joho/godotenv"
)
-var Config ServerConfig
+var Config types.ServerConfig
func init() {
godotenv.Load()
- Config = ServerConfig{
+ Config = types.ServerConfig{
Port: getEnvAsInt("PORT"),
- Provider: Provider(getEnv("PROVIDER")),
+ Provider: types.Provider(getEnv("PROVIDER")),
Model: getEnv("MODEL"),
APIKey: getEnv("API_KEY"),
}
@@ -25,7 +26,7 @@ func init() {
}
if Config.Provider == "" {
- Config.Provider = Gemini
+ Config.Provider = types.Gemini
}
if Config.Model == "" {
diff --git a/generators/gemini/gemini.go b/generators/gemini/gemini.go
new file mode 100644
index 0000000..8b24475
--- /dev/null
+++ b/generators/gemini/gemini.go
@@ -0,0 +1,67 @@
+package gemini
+
+import (
+ "context"
+ "errors"
+ "thunderbird-ai-compose-server/config"
+
+ "github.com/google/generative-ai-go/genai"
+ "google.golang.org/api/option"
+)
+
+func GenerateResponse(prompt string) (string, error) {
+ ctx := context.Background()
+
+ client, err := genai.NewClient(ctx, option.WithAPIKey(config.Config.APIKey))
+ if err != nil {
+ return "", err
+ }
+
+ defer client.Close()
+
+ model := client.GenerativeModel(config.Config.Model)
+ model.SafetySettings = []*genai.SafetySetting{
+ {
+ Category: genai.HarmCategoryHarassment,
+ Threshold: genai.HarmBlockNone,
+ },
+ {
+ Category: genai.HarmCategoryHateSpeech,
+ Threshold: genai.HarmBlockNone,
+ },
+ {
+ Category: genai.HarmCategorySexuallyExplicit,
+ Threshold: genai.HarmBlockNone,
+ },
+ {
+ Category: genai.HarmCategoryDangerousContent,
+ Threshold: genai.HarmBlockNone,
+ },
+ }
+
+ resp, err := model.GenerateContent(ctx, genai.Text(prompt))
+ if err != nil {
+ return "", err
+ }
+
+ if len(resp.Candidates) == 0 {
+ return "", errors.New("no response candidates received")
+ }
+
+ candidate := resp.Candidates[0]
+
+ if candidate.Content == nil {
+ return "", errors.New("no content in the response candidate")
+ }
+
+ if len(candidate.Content.Parts) == 0 {
+ return "", errors.New("no parts in the response content")
+ }
+
+ if textPart, ok := candidate.Content.Parts[0].(genai.Text); ok {
+ result := string(textPart)
+ return result, nil
+ }
+
+ return "", errors.New("response part is not text")
+}
diff --git a/generators/prompt.go b/generators/prompt.go
new file mode 100644
index 0000000..8e7fbd1
--- /dev/null
+++ b/generators/prompt.go
@@ -0,0 +1,80 @@
+package generators
+
+import (
+ "strings"
+ "thunderbird-ai-compose-server/types"
+)
+
+const systemPrompt = `You are a professional email writing assistant integrated into the Thunderbird email client.
+Your goal is to help users compose high-quality emails based on their prompts and context. Follow these guidelines:
+1. Understand the user's prompt and the context of the email, including any provided details such as subject, recipients, and body content.
+2. Understand the writing style the user is going for, whether formal, informal, concise, or detailed.
+3. Emails can be in plain text or HTML format. Ensure the output matches the specified format. If HTML is requested, use appropriate HTML tags for formatting.
+4. You only need to generate the email body or the reply in case of a reply.
+5. If the user provides a draft or partial content, use it as a base and enhance it according to the prompt.
+6. Ensure the email is clear, coherent, and free of grammatical errors.
+7. If the prompt is vague or lacks details, make reasonable assumptions based on common email practices.
+8. Always maintain a professional tone unless the prompt specifies otherwise.
+9. You only need to output plain text or HTML content of the email body. Do not include any additional explanations or code blocks like ` + "```" + `html` + `, etc.
+10. You should not include HTML syntax like DOCTYPE or <head> or <html> or <body> tags in the output. Only include the content that goes inside the <body> tag if HTML is requested, like <p>, <br>, <b>, <i>, etc.
+
+Examples:
+- If the user wants to write a formal business email, ensure the tone is professional and polite.
+- For informal emails to friends or family, a casual and friendly tone is appropriate.
+- When replying to an email, ensure you address all points raised in the original message.
+
+Remember, your primary objective is to assist users in crafting effective emails that meet their needs and expectations.
+
+Following context is provided along with every request automatically:
+- Account details (email, name) of the user sending the email. You can use this to personalize the email.
+- The subject of the email. You can use this to ensure the email is relevant to the topic. The subject can also be empty.
+- The people the email is being sent to (To, Cc, Bcc). Further personalization can be done if needed using this information. This information can also be empty.
+- The body of the email in plain text. This can also be empty. If this is a reply, this will contain the original email being replied to.
+- The body of the email in HTML (if applicable). This can also be empty. If this is a reply, this will contain the HTML of the original email being replied to.
+- Identity ID of the user (this is inserted by Thunderbird, and can be ignored).
+- Whether the email is in HTML format or plain text. Use this to determine if you need to generate HTML or plain text.
+
+Use the above guidelines and context to generate high-quality emails that meet the user's requirements.
+`
+
+func BuildPrompt(payload types.Payload) string {
+ var prompt strings.Builder
+
+ prompt.WriteString(systemPrompt)
+ prompt.WriteString("\n\n")
+ prompt.WriteString("User Prompt:\n")
+ prompt.WriteString(payload.Prompt)
+ prompt.WriteString("\n\n")
+ prompt.WriteString("Email Context:\n")
+ if payload.Context.Compose.Subject != "" {
+ prompt.WriteString("Subject: " + payload.Context.Compose.Subject + "\n")
+ }
+ if len(payload.Context.Compose.To) > 0 {
+ prompt.WriteString("To: " + strings.Join(payload.Context.Compose.To, ", ") + "\n")
+ }
+ if len(payload.Context.Compose.Cc) > 0 {
+ prompt.WriteString("Cc: " + strings.Join(payload.Context.Compose.Cc, ", ") + "\n")
+ }
+ if len(payload.Context.Compose.Bcc) > 0 {
+ prompt.WriteString("Bcc: " + strings.Join(payload.Context.Compose.Bcc, ", ") + "\n")
+ }
+ if payload.Context.Account.Name != "" {
+ prompt.WriteString("Sender Name: " + payload.Context.Account.Name + "\n")
+ }
+ if payload.Context.Account.Email != "" {
+ prompt.WriteString("Sender Email: " + payload.Context.Account.Email + "\n")
+ }
+ if payload.Context.Compose.BodyPlain != "" {
+ prompt.WriteString("Body (Plain Text):\n" + payload.Context.Compose.BodyPlain + "\n")
+ }
+ if payload.Context.Compose.BodyHTML != "" {
+ prompt.WriteString("Body (HTML):\n" + payload.Context.Compose.BodyHTML + "\n")
+ }
+ if payload.Context.Compose.IsHTML {
+ prompt.WriteString("Is HTML: true. The email should be in HTML format.\n")
+ } else {
+ prompt.WriteString("Is HTML: false. The email should be in plain text format.\n")
+ }
+
+ return prompt.String()
+}
diff --git a/go.mod b/go.mod
index d742779..b2d5b2d 100644
--- a/go.mod
+++ b/go.mod
@@ -4,12 +4,26 @@ go 1.25.1
require (
github.com/gofiber/fiber/v2 v2.52.9
+ github.com/google/generative-ai-go v0.20.1
github.com/joho/godotenv v1.5.1
+ google.golang.org/api v0.249.0
)
require (
+ cloud.google.com/go v0.115.0 // indirect
+ cloud.google.com/go/ai v0.8.0 // indirect
+ cloud.google.com/go/auth v0.16.5 // indirect
+ cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
+ cloud.google.com/go/compute/metadata v0.8.0 // indirect
+ cloud.google.com/go/longrunning v0.5.7 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
+ github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -18,5 +32,21 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
- golang.org/x/sys v0.28.0 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
+ go.opentelemetry.io/otel v1.37.0 // indirect
+ go.opentelemetry.io/otel/metric v1.37.0 // indirect
+ go.opentelemetry.io/otel/trace v1.37.0 // indirect
+ golang.org/x/crypto v0.41.0 // indirect
+ golang.org/x/net v0.43.0 // indirect
+ golang.org/x/oauth2 v0.30.0 // indirect
+ golang.org/x/sync v0.16.0 // indirect
+ golang.org/x/sys v0.35.0 // indirect
+ golang.org/x/text v0.28.0 // indirect
+ golang.org/x/time v0.12.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
+ google.golang.org/grpc v1.75.0 // indirect
+ google.golang.org/protobuf v1.36.8 // indirect
)
diff --git a/go.sum b/go.sum
index 1ac26d0..bbd0da8 100644
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,42 @@
+cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
+cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
+cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
+cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
+cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
+cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=
+cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
+cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
+cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=
+cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
+cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
+cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
+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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ=
+github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
+github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
+github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
+github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
+github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
@@ -15,15 +48,63 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
+go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
+go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
+go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
+go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
+go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
+go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
+go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
+go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
+go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
+go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
+golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
+golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
+golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
+golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
+golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
+golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
+golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
+google.golang.org/api v0.249.0 h1:0VrsWAKzIZi058aeq+I86uIXbNhm9GxSHpbmZ92a38w=
+google.golang.org/api v0.249.0/go.mod h1:dGk9qyI0UYPwO/cjt2q06LG/EhUpwZGdAbYF14wHHrQ=
+google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
+google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
+google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
+google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
+google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
+google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
+google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
+google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
+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
index cc0fef7..aa5ae3c 100644
--- a/main.go
+++ b/main.go
@@ -2,31 +2,21 @@ package main
import (
"log"
+ "thunderbird-ai-compose-server/config"
+ "thunderbird-ai-compose-server/routers"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
+ "github.com/gofiber/fiber/v2/middleware/logger"
)
func main() {
app := fiber.New()
app.Use(cors.New())
+ app.Use(logger.New())
+ routers.Setup(app)
- app.Post("/generate", func(c *fiber.Ctx) error {
- var payload Payload
-
- if err := c.BodyParser(&payload); err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
- "error": "Invalid request payload",
- })
- }
-
- return c.JSON(fiber.Map{
- "response": "Payload received successfully",
- "data": payload,
- })
- })
-
- log.Printf("Starting server on port %d\n", Config.Port)
+ log.Printf("Starting server on port %d\n", config.Config.Port)
log.Fatal(app.Listen(":3000"))
}
diff --git a/routers/router.go b/routers/router.go
new file mode 100644
index 0000000..cae38db
--- /dev/null
+++ b/routers/router.go
@@ -0,0 +1,53 @@
+package routers
+
+import (
+ "errors"
+ "thunderbird-ai-compose-server/config"
+ "thunderbird-ai-compose-server/generators"
+ "thunderbird-ai-compose-server/generators/gemini"
+ "thunderbird-ai-compose-server/types"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+func Setup(router *fiber.App) {
+ router.Post("/generate", func(c *fiber.Ctx) error {
+ var payload types.Payload
+
+ if err := c.BodyParser(&payload); err != nil {
+ return c.Status(fiber.StatusBadRequest).JSON(types.ErrorResponse{
+ Error: "Invalid request payload",
+ })
+ }
+
+ prompt := generators.BuildPrompt(payload)
+
+ var response string
+ var err error
+
+ switch config.Config.Provider {
+ case types.Gemini:
+ response, err = gemini.GenerateResponse(prompt)
+ default:
+ err = errors.New("unsupported AI provider")
+ }
+
+ if err != nil {
+ return c.Status(fiber.StatusInternalServerError).JSON(types.ErrorResponse{
+ Error: err.Error(),
+ })
+ }
+
+ return c.JSON(types.SuccessResponse{
+ Response: response,
+ Payload: payload,
+ })
+ })
+
+ // 404 default
+ router.Use(func(c *fiber.Ctx) error {
+ return c.Status(fiber.StatusNotFound).JSON(types.ErrorResponse{
+ Error: "Endpoint not found",
+ })
+ })
+}
diff --git a/types.go b/types/types.go
index 903705d..955d085 100644
--- a/types.go
+++ b/types/types.go
@@ -1,4 +1,4 @@
-package main
+package types
type Provider string
@@ -40,3 +40,12 @@ type Payload struct {
Prompt string `json:"prompt"`
Context ComposeContext `json:"context"`
}
+
+type SuccessResponse struct {
+ Response string `json:"response"`
+ Payload Payload `json:"payload"`
+}
+
+type ErrorResponse struct {
+ Error string `json:"error"`
+}