feat(cover): add cover letter name generation

This commit is contained in:
Leons Aleksandrovs
2025-07-12 15:21:42 +03:00
parent e7fae1387f
commit 7d8566b18b
2 changed files with 31 additions and 17 deletions

View File

@@ -51,13 +51,13 @@ func Post(c *gin.Context) {
}
// Call chat and ask for cover letter nicely
coverLetter, err := chatgpt.GenerateCoverLetter(templates[0].Template, data.Application)
generatedCover, err := chatgpt.GenerateCoverLetter(templates[0].Template, data.Application)
if err != nil {
res.Error(c, err.Error(), http.StatusInternalServerError)
return
}
res.Success(c, coverLetter)
res.Success(c, generatedCover)
}
func Put(c *gin.Context) {

View File

@@ -10,9 +10,14 @@ import (
"time"
)
type ResponseFormat struct {
Type string `json:"type"`
}
type ChatRequest struct {
Model string `json:"model"`
Messages []ChatMessage `json:"messages"`
Model string `json:"model"`
Messages []ChatMessage `json:"messages"`
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
}
type ChatMessage struct {
@@ -20,40 +25,44 @@ type ChatMessage struct {
Content string `json:"content"`
}
func GenerateCoverLetter(templateHTML string, jobHTML string) (string, error) {
type GeneratedCover struct {
Name string `json:"name"`
Cover string `json:"cover"`
}
func GenerateCoverLetter(templateHTML string, jobHTML string) (GeneratedCover, error) {
apiKey := config.Env["CHATGPT_KEY"]
if apiKey == "" {
return "", errors.New("no API key chat gpt provided")
return GeneratedCover{}, errors.New("no API key chat gpt provided")
}
payload := ChatRequest{
Model: "gpt-4o", // o4-mini
ResponseFormat: &ResponseFormat{Type: "json_object"},
Messages: []ChatMessage{
{
Role: "system",
Content: `You are a helpful assistant that fills out cover letter templates in HTML format. Replace all <...> tags like <company>, <experience>, etc., with appropriate content based on the job application.`,
Content: `You are a helpful assistant that fills out cover letter templates in HTML format and provides a name for it. Replace all <...> tags like <company>, <experience>, etc., with appropriate content based on the job application. You must respond with a JSON object with two keys: "name" for the cover letter title (e.g., "Cover Letter for a Software Engineer"), and "cover" for the filled HTML cover letter.`,
},
{
Role: "user",
Content: fmt.Sprintf(`Template:
Content: fmt.Sprintf(`Template:
%s
Job description:
%s
Respond with ONLY the filled HTML cover letter.`, templateHTML, jobHTML),
%s`, templateHTML, jobHTML),
},
},
}
body, err := json.Marshal(payload)
if err != nil {
return "", err
return GeneratedCover{}, err
}
req, err := http.NewRequest("POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(body))
if err != nil {
return "", err
return GeneratedCover{}, err
}
req.Header.Set("Content-Type", "application/json")
@@ -62,7 +71,7 @@ Respond with ONLY the filled HTML cover letter.`, templateHTML, jobHTML),
client := &http.Client{Timeout: 20 * time.Second}
res, err := client.Do(req)
if err != nil {
return "", err
return GeneratedCover{}, err
}
defer res.Body.Close()
@@ -75,12 +84,17 @@ Respond with ONLY the filled HTML cover letter.`, templateHTML, jobHTML),
}
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
return "", err
return GeneratedCover{}, err
}
if len(result.Choices) == 0 {
return "", fmt.Errorf("no response from GPT")
return GeneratedCover{}, fmt.Errorf("no response from GPT")
}
return result.Choices[0].Message.Content, nil
var cover GeneratedCover
if err := json.Unmarshal([]byte(result.Choices[0].Message.Content), &cover); err != nil {
return GeneratedCover{}, fmt.Errorf("failed to parse GPT response: %w", err)
}
return cover, nil
}