Integrated AI translation

This commit is contained in:
Mazyar
2026-05-08 16:17:33 +03:30
parent 65a19d1514
commit b480ed2371
9 changed files with 357 additions and 24 deletions
+82
View File
@@ -79,6 +79,47 @@ func (c *Client) FetchSummaryOnDemand(ctx context.Context, reqBody SummarizeRequ
return nil, fmt.Errorf("ai_summarizer: all %d attempts failed, last error: %w", c.config.APIRetryCount+1, lastErr)
}
// FetchTranslationOnDemand calls the external AI service POST /ai/translate endpoint
// to generate title/description translation on demand.
func (c *Client) FetchTranslationOnDemand(ctx context.Context, reqBody TranslateRequest) (*TranslateResponse, error) {
jsonBody, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("ai_summarizer: failed to marshal translation request body: %w", err)
}
url := c.config.APIBaseURL + "/ai/translate"
var lastErr error
for attempt := 0; attempt <= c.config.APIRetryCount; attempt++ {
if attempt > 0 {
c.logger.Warn("Retrying AI translate request", map[string]interface{}{
"notice_id": reqBody.NoticeID,
"attempt": attempt,
})
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(c.config.APIRetryDelay):
}
}
resp, err := c.doTranslatePost(ctx, url, jsonBody)
if err != nil {
lastErr = err
c.logger.Error("AI translate request failed", map[string]interface{}{
"notice_id": reqBody.NoticeID,
"attempt": attempt,
"error": err.Error(),
})
continue
}
return resp, nil
}
return nil, fmt.Errorf("ai_summarizer: all %d translation attempts failed, last error: %w", c.config.APIRetryCount+1, lastErr)
}
// doPost performs a single POST request and parses the response.
func (c *Client) doPost(ctx context.Context, url string, jsonBody []byte) (*SummarizeResponse, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(jsonBody))
@@ -123,3 +164,44 @@ func (c *Client) doPost(ctx context.Context, url string, jsonBody []byte) (*Summ
return &result, nil
}
// doTranslatePost performs a single translation POST request and parses the response.
func (c *Client) doTranslatePost(ctx context.Context, url string, jsonBody []byte) (*TranslateResponse, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(jsonBody))
if err != nil {
return nil, fmt.Errorf("ai_summarizer: failed to create translation request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
c.logger.Debug("Sending AI translate request", map[string]interface{}{
"url": url,
})
httpResp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("ai_summarizer: translation request error: %w", err)
}
defer httpResp.Body.Close()
bodyBytes, err := io.ReadAll(httpResp.Body)
if err != nil {
return nil, fmt.Errorf("ai_summarizer: failed to read translation response body: %w", err)
}
if httpResp.StatusCode >= 400 {
return nil, fmt.Errorf("%w: status %d, body: %s", ErrAPINonSuccess, httpResp.StatusCode, string(bodyBytes))
}
var result TranslateResponse
if err := json.Unmarshal(bodyBytes, &result); err != nil {
return nil, fmt.Errorf("ai_summarizer: failed to decode translation response JSON: %w", err)
}
c.logger.Info("AI translate request succeeded", map[string]interface{}{
"notice_id": result.NoticeID,
"language": result.Language,
})
return &result, nil
}
+17
View File
@@ -30,6 +30,23 @@ type DocumentSummary struct {
Error string `json:"error"` // empty string when no error
}
// TranslateRequest represents the request payload sent to the AI service
// POST /ai/translate endpoint.
type TranslateRequest struct {
NoticeID string `json:"notice_id"` // required
Title string `json:"title"` // required
Description string `json:"description"` // required
Language string `json:"language,omitempty"` // optional target language override
}
// TranslateResponse represents the response from the AI translation API.
type TranslateResponse struct {
NoticeID string `json:"notice_id"`
TranslatedTitle string `json:"translated_title"`
TranslatedDescription string `json:"translated_description"`
Language string `json:"language"`
}
// TenderJSON represents the structure of the <notice_id>/tender.json file
// stored in the AI service's MinIO bucket.
type TenderJSON struct {