diff options
| author | Bobby <[email protected]> | 2025-09-24 19:50:14 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-09-24 19:50:14 +0530 |
| commit | 935d3a22363cac775d0f5f6757367ffbecf5183f (patch) | |
| tree | 687dfa53a89eeea2d4390d39dad0a488bc37e5d0 | |
| parent | a42336fec9c5ed6cd47e7dc0437c931ae06cfc0a (diff) | |
| download | thunderbird-ai-compose-server-935d3a22363cac775d0f5f6757367ffbecf5183f.tar.xz thunderbird-ai-compose-server-935d3a22363cac775d0f5f6757367ffbecf5183f.zip | |
added gemini generator. cleaned up folder structure
| -rw-r--r-- | config/config.go (renamed from config.go) | 11 | ||||
| -rw-r--r-- | generators/gemini/gemini.go | 67 | ||||
| -rw-r--r-- | generators/prompt.go | 80 | ||||
| -rw-r--r-- | go.mod | 32 | ||||
| -rw-r--r-- | go.sum | 85 | ||||
| -rw-r--r-- | main.go | 22 | ||||
| -rw-r--r-- | routers/router.go | 53 | ||||
| -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() +} @@ -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 ) @@ -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= @@ -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"` +} |
