Repository: icereed/paperless-gpt
Pull request #303: Generate created date
Author: login: AxcellF
Created: 2025-03-22T19:34:56Z | Merged: 2025-03-23T07:47:10Z

**New feature**
Adds the option to have LLMs generate a created date. Works just the same as the other options: 
- Checkbox on the front end to enable/disable created date generation
- User can override the LLM's suggestion by entering another date when manually triggering the generation
- Environment variable to set default (on/off)
- Prompt .tmpl file (`created_date_prompt.tmpl`)

README.md has been updated, and basic tests cases for the backend has been implemented.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced automatic creation date generation for documents alongside title and tag generation.
  - Enhanced document metadata now displays a suggested created date that users can review and modify.
  
- **Documentation**
  - Updated documentation to detail the new automatic creation date functionality and its configuration options.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/README.md b/README.md
index 65652f43..caa58527 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ https://github.com/user-attachments/assets/bd5d38b9-9309-40b9-93ca-918dfa4f3fd4
    - **Google Document AI**: Leverage Google's powerful Document AI for OCR tasks.
    - **Azure Document Intelligence**: Use Microsoft's enterprise OCR solution.
 
-3. **Automatic Title & Tag Generation**  
+3. **Automatic Title, Tag & Created Date Generation**  
    No more guesswork. Let the AI do the naming and categorizing. You can easily review suggestions and refine them if needed.
 
 4. **Supports DeepSeek reasoning models in Ollama**  
@@ -279,6 +279,7 @@ paperless-gpt supports three different OCR providers, each with unique strengths
 | `AUTO_GENERATE_TITLE`            | Generate titles automatically if `paperless-gpt-auto` is used.                                                   | No       | true                   |
 | `AUTO_GENERATE_TAGS`             | Generate tags automatically if `paperless-gpt-auto` is used.                                                     | No       | true                   |
 | `AUTO_GENERATE_CORRESPONDENTS`   | Generate correspondents automatically if `paperless-gpt-auto` is used.                                           | No       | true                   |
+| `AUTO_GENERATE_CREATED_DATE`     | Generate the created dates automatically if `paperless-gpt-auto` is used.                                        | No       | true                   |
 | `OCR_LIMIT_PAGES`                | Limit the number of pages for OCR. Set to `0` for no limit.                                                      | No       | 5                      |
 | `TOKEN_LIMIT`                    | Maximum tokens allowed for prompts/content. Set to `0` to disable limit. Useful for smaller LLMs.                | No       |                        |
 | `CORRESPONDENT_BLACK_LIST`       | A comma-separated list of names to exclude from the correspondents suggestions. Example: `John Doe, Jane Smith`. | No       |                        |
@@ -291,6 +292,7 @@ paperless-gpt's flexible **prompt templates** let you shape how AI responds:
 2. **`tag_prompt.tmpl`**: For tagging logic.
 3. **`ocr_prompt.tmpl`**: For LLM OCR.
 4. **`correspondent_prompt.tmpl`**: For correspondent identification.
+5. **`created_date_prompt.tmpl`**: For setting of document's created date.
 
 Mount them into your container via:
 
@@ -327,6 +329,10 @@ Each template has access to specific variables:
 - `{{.Title}}` - Document title
 - `{{.Content}}` - Document content text
 
+**created_date_prompt.tmpl**:
+- `{{.Language}}` - Target language
+- `{{.Content}}` - Document content text
+
 The templates use Go's text/template syntax. paperless-gpt automatically reloads template changes on startup.
 
 ---
diff --git a/app_llm.go b/app_llm.go
index 8a385283..894936e6 100644
--- a/app_llm.go
+++ b/app_llm.go
@@ -278,6 +278,61 @@ func (app *App) getSuggestedTitle(ctx context.Context, content string, originalT
 	return strings.TrimSpace(strings.Trim(result, "\"")), nil
 }
 
+// getSuggestedCreatedDate generates a suggested createdDate for a document using the LLM
+func (app *App) getSuggestedCreatedDate(ctx context.Context, content string, logger *logrus.Entry) (string, error) {
+	likelyLanguage := getLikelyLanguage()
+
+	templateMutex.RLock()
+	defer templateMutex.RUnlock()
+
+	// Get available tokens for content
+	templateData := map[string]interface{}{
+		"Language": likelyLanguage,
+		"Content":  content,
+	}
+
+	availableTokens, err := getAvailableTokensForContent(createdDateTemplate, templateData)
+	if err != nil {
+		logger.Errorf("Error calculating available tokens: %v", err)
+		return "", fmt.Errorf("error calculating available tokens: %v", err)
+	}
+
+	// Truncate content if needed
+	truncatedContent, err := truncateContentByTokens(content, availableTokens)
+	if err != nil {
+		logger.Errorf("Error truncating content: %v", err)
+		return "", fmt.Errorf("error truncating content: %v", err)
+	}
+
+	// Execute template with truncated content
+	var promptBuffer bytes.Buffer
+	templateData["Content"] = truncatedContent
+	err = createdDateTemplate.Execute(&promptBuffer, templateData)
+
+	if err != nil {
+		return "", fmt.Errorf("error executing createdDate template: %v", err)
+	}
+
+	prompt := promptBuffer.String()
+	logger.Debugf("CreatedDate suggestion prompt: %s", prompt)
+
+	completion, err := app.LLM.GenerateContent(ctx, []llms.MessageContent{
+		{
+			Parts: []llms.ContentPart{
+				llms.TextContent{
+					Text: prompt,
+				},
+			},
+			Role: llms.ChatMessageTypeHuman,
+		},
+	})
+	if err != nil {
+		return "", fmt.Errorf("error getting response from LLM: %v", err)
+	}
+	result := stripReasoning(completion.Choices[0].Content)
+	return strings.TrimSpace(strings.Trim(result, "\"")), nil
+}
+
 // generateDocumentSuggestions generates suggestions for a set of documents
 func (app *App) generateDocumentSuggestions(ctx context.Context, suggestionRequest GenerateSuggestionsRequest, logger *logrus.Entry) ([]DocumentSuggestion, error) {
 	// Fetch all available tags from paperless-ngx
@@ -326,6 +381,7 @@ func (app *App) generateDocumentSuggestions(ctx context.Context, suggestionReque
 			suggestedTitle := doc.Title
 			var suggestedTags []string
 			var suggestedCorrespondent string
+			var suggestedCreatedDate string
 
 			if suggestionRequest.GenerateTitles {
 				suggestedTitle, err = app.getSuggestedTitle(ctx, content, suggestedTitle, docLogger)
@@ -360,6 +416,17 @@ func (app *App) generateDocumentSuggestions(ctx context.Context, suggestionReque
 				}
 			}
 
+			if suggestionRequest.GenerateCreatedDate {
+				suggestedCreatedDate, err = app.getSuggestedCreatedDate(ctx, content, docLogger)
+				if err != nil {
+					mu.Lock()
+					errorsList = append(errorsList, fmt.Errorf("Document %d: %v", documentID, err))
+					mu.Unlock()
+					log.Errorf("Error generating createdDate for document %d: %v", documentID, err)
+					return
+				}
+			}
+
 			mu.Lock()
 			suggestion := DocumentSuggestion{
 				ID:               documentID,
@@ -388,6 +455,14 @@ func (app *App) generateDocumentSuggestions(ctx context.Context, suggestionReque
 			} else {
 				suggestion.SuggestedCorrespondent = ""
 			}
+
+			// CreatedDate
+			if suggestionRequest.GenerateCreatedDate {
+				log.Printf("Suggested createdDate for document %d: %s", documentID, suggestedCreatedDate)
+				suggestion.SuggestedCreatedDate = suggestedCreatedDate
+			} else {
+				suggestion.SuggestedCreatedDate = ""
+			}
 			// Remove manual tag from the list of suggested tags
 			suggestion.RemoveTags = []string{manualTag, autoTag}
 
diff --git a/app_llm_test.go b/app_llm_test.go
index da94e11b..e5898fda 100644
--- a/app_llm_test.go
+++ b/app_llm_test.go
@@ -54,6 +54,10 @@ Content: {{.Content}}
 	testCorrespondentTemplate = `
 Language: {{.Language}}
 Content: {{.Content}}
+`
+	testCreatedDateContentTemplate = `
+Language: {{.Language}}
+Content: {{.Content}}
 `
 )
 
@@ -68,6 +72,8 @@ func TestPromptTokenLimits(t *testing.T) {
 	require.NoError(t, err)
 	correspondentTemplate, err = template.New("correspondent").Parse(testCorrespondentTemplate)
 	require.NoError(t, err)
+	createdDateTemplate, err = template.New("created_date").Parse(testCreatedDateContentTemplate)
+	require.NoError(t, err)
 
 	// Save current env and restore after test
 	originalLimit := os.Getenv("TOKEN_LIMIT")
@@ -266,6 +272,43 @@ func TestTokenLimitInTitleGeneration(t *testing.T) {
 	// Final prompt should be within token limit
 	assert.LessOrEqual(t, len(tokens), 50, "Final prompt should be within token limit")
 }
+
+func TestTokenLimitInCreatedDateGeneration(t *testing.T) {
+	testLogger := logrus.WithField("test", "test")
+
+	// Save current env and restore after test
+	originalLimit := os.Getenv("TOKEN_LIMIT")
+	defer os.Setenv("TOKEN_LIMIT", originalLimit)
+
+	// Create a test app with mock LLM
+	mockLLM := &mockLLM{}
+	app := &App{
+		LLM: mockLLM,
+	}
+
+	// Test content that would exceed reasonable token limits
+	longContent := "This is a very long content that would normally exceed token limits. " +
+		"It contains multiple sentences and should be truncated appropriately."
+
+	// Set a small token limit
+	os.Setenv("TOKEN_LIMIT", "50")
+	resetTokenLimit()
+
+	// Call getSuggestedCreatedDate
+	ctx := context.Background()
+
+	_, err := app.getSuggestedCreatedDate(ctx, longContent, testLogger)
+	require.NoError(t, err)
+
+	// Verify the final prompt size
+	splitter := textsplitter.NewTokenSplitter()
+	tokens, err := splitter.SplitText(mockLLM.lastPrompt)
+	require.NoError(t, err)
+
+	// Final prompt should be within token limit
+	assert.LessOrEqual(t, len(tokens), 50, "Final prompt should be within token limit")
+}
+
 func TestStripReasoning(t *testing.T) {
 	tests := []struct {
 		name     string
diff --git a/go.mod b/go.mod
index f2254fbf..0424ea19 100644
--- a/go.mod
+++ b/go.mod
@@ -17,58 +17,58 @@ require (
 	github.com/stretchr/testify v1.10.0
 	github.com/tmc/langchaingo v0.1.13
 	golang.org/x/sync v0.12.0
-	google.golang.org/api v0.225.0
+	google.golang.org/api v0.226.0
 	gorm.io/driver/sqlite v1.5.7
 	gorm.io/gorm v1.25.12
 )
 
 require (
-	cloud.google.com/go v0.118.3 // indirect
+	cloud.google.com/go v0.119.0 // indirect
 	cloud.google.com/go/auth v0.15.0 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
 	cloud.google.com/go/compute/metadata v0.6.0 // indirect
-	cloud.google.com/go/longrunning v0.6.5 // indirect
+	cloud.google.com/go/longrunning v0.6.6 // indirect
 	dario.cat/mergo v1.0.1 // indirect
 	github.com/Masterminds/goutils v1.1.1 // indirect
-	github.com/Masterminds/semver/v3 v3.3.0 // indirect
-	github.com/bytedance/sonic v1.11.6 // indirect
-	github.com/bytedance/sonic/loader v0.1.1 // indirect
-	github.com/cloudwego/base64x v0.1.4 // indirect
+	github.com/Masterminds/semver/v3 v3.3.1 // indirect
+	github.com/bytedance/sonic v1.13.1 // indirect
+	github.com/bytedance/sonic/loader v0.2.4 // indirect
+	github.com/cloudwego/base64x v0.1.5 // indirect
 	github.com/cloudwego/iasm v0.2.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/dlclark/regexp2 v1.10.0 // indirect
-	github.com/ebitengine/purego v0.8.0 // indirect
+	github.com/dlclark/regexp2 v1.11.5 // indirect
+	github.com/ebitengine/purego v0.8.2 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
-	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/gin-contrib/sse v1.0.0 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
-	github.com/go-playground/validator/v10 v10.20.0 // indirect
-	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/go-playground/validator/v10 v10.25.0 // indirect
+	github.com/goccy/go-json v0.10.5 // indirect
 	github.com/google/s2a-go v0.1.9 // indirect
-	github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect
+	github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
 	github.com/googleapis/gax-go/v2 v2.14.1 // indirect
 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 	github.com/huandu/xstrings v1.5.0 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
-	github.com/jupiterrider/ffi v0.2.0 // indirect
-	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+	github.com/jupiterrider/ffi v0.4.0 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.10 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
-	github.com/mattn/go-colorable v0.1.13 // indirect
+	github.com/mattn/go-colorable v0.1.14 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mattn/go-sqlite3 v1.14.24 // indirect
 	github.com/mitchellh/copystructure v1.2.0 // indirect
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
-	github.com/pkoukk/tiktoken-go v0.1.6 // indirect
+	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
+	github.com/pkoukk/tiktoken-go v0.1.7 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/shopspring/decimal v1.4.0 // indirect
-	github.com/spf13/cast v1.7.0 // indirect
+	github.com/spf13/cast v1.7.1 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.12 // indirect
 	gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 // indirect
@@ -77,21 +77,21 @@ require (
 	gitlab.com/golang-commonmark/mdurl v0.0.0-20191124015652-932350d1cb84 // indirect
 	gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
-	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
-	go.opentelemetry.io/otel v1.34.0 // indirect
-	go.opentelemetry.io/otel/metric v1.34.0 // indirect
-	go.opentelemetry.io/otel/trace v1.34.0 // indirect
-	golang.org/x/arch v0.8.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
+	go.opentelemetry.io/otel v1.35.0 // indirect
+	go.opentelemetry.io/otel/metric v1.35.0 // indirect
+	go.opentelemetry.io/otel/trace v1.35.0 // indirect
+	golang.org/x/arch v0.15.0 // indirect
 	golang.org/x/crypto v0.36.0 // indirect
 	golang.org/x/net v0.37.0 // indirect
 	golang.org/x/oauth2 v0.28.0 // indirect
 	golang.org/x/sys v0.31.0 // indirect
 	golang.org/x/text v0.23.0 // indirect
 	golang.org/x/time v0.11.0 // indirect
-	google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
+	google.golang.org/genproto v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
 	google.golang.org/grpc v1.71.0 // indirect
 	google.golang.org/protobuf v1.36.5 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 2a099987..5795238e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
 cloud.google.com/go v0.118.1 h1:b8RATMcrK9A4BH0rj8yQupPXp+aP+cJ0l6H7V9osV1E=
 cloud.google.com/go v0.118.1/go.mod h1:CFO4UPEPi8oV21xoezZCrd3d81K4fFkDTEJu4R8K+9M=
-cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
-cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
+cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE=
+cloud.google.com/go v0.119.0/go.mod h1:fwB8QLzTcNevxqi8dcpR+hoMIs3jBherGS9VUBDAW08=
 cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
 cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
 cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
@@ -14,22 +14,31 @@ cloud.google.com/go/documentai v1.35.3 h1:aLEGWraq48QX/G6or4T8QtpaX66SES3+b87TrU
 cloud.google.com/go/documentai v1.35.3/go.mod h1:LsX1RO08WDd8mFBviYB03jgCytz2oIcwIZ9lBw5bKiM=
 cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg=
 cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs=
-cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q=
-cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY=
+cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw=
+cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw=
 dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
 dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
 github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
 github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
 github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
+github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
+github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
 github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
 github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
 github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
 github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
+github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
+github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
 github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
+github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
 github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
 github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
+github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
 github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -37,8 +46,12 @@ 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/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
 github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
+github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE=
 github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
+github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
 github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
 github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -51,6 +64,8 @@ github.com/gen2brain/go-fitz v1.24.14 h1:09weRkjVtLYNGo7l0J7DyOwBExbwi8SJ9h8YPhw
 github.com/gen2brain/go-fitz v1.24.14/go.mod h1:0KaZeQgASc20Yp5R/pFzyy7SmP01XcoHKNF842U2/S4=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
+github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
 github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
 github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -66,8 +81,12 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
 github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
+github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -81,6 +100,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gT
 github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
 github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g=
 github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
+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.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
 github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
 github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
@@ -99,9 +120,13 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jupiterrider/ffi v0.2.0 h1:tMM70PexgYNmV+WyaYhJgCvQAvtTCs3wXeILPutihnA=
 github.com/jupiterrider/ffi v0.2.0/go.mod h1:yqYqX5DdEccAsHeMn+6owkoI2llBLySVAF8dwCDZPVs=
+github.com/jupiterrider/ffi v0.4.0 h1:7mhlrfiBZa0kHhh2DV7mGAdXN/D8zDeu8UlaBO+ZSko=
+github.com/jupiterrider/ffi v0.4.0/go.mod h1:1QCaf2VVPpGyIeU3RqQ2rHYrAPT8m9l0GhQupVYQB24=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
+github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -111,6 +136,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
@@ -127,8 +154,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
 github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw=
 github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
+github.com/pkoukk/tiktoken-go v0.1.7 h1:qOBHXX4PHtvIvmOtyg1EeKlwFRiMKAcoMp4Q+bLQDmw=
+github.com/pkoukk/tiktoken-go v0.1.7/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
 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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
@@ -141,6 +172,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
 github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
+github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -176,21 +209,34 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
 go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
 go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
 go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
+go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
+go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
 go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
 go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
+go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
+go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
 go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
 go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
+go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
 go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
 go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
 go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
 go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
+go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
+go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
 golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
+golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
 golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
 golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
 golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
@@ -233,20 +279,24 @@ google.golang.org/api v0.224.0 h1:Ir4UPtDsNiwIOHdExr3fAj4xZ42QjK7uQte3lORLJwU=
 google.golang.org/api v0.224.0/go.mod h1:3V39my2xAGkodXy0vEqcEtkqgw2GtrFL5WuBZlCTCOQ=
 google.golang.org/api v0.225.0 h1:+4/IVqBQm0MV5S+JW3kdEGC1WtOmM2mXN1LKH1LdNlw=
 google.golang.org/api v0.225.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY=
+google.golang.org/api v0.226.0 h1:9A29y1XUD+YRXfnHkO66KggxHBZWg9LsTGqm7TkUvtQ=
+google.golang.org/api v0.226.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY=
 google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 h1:Pw6WnI9W/LIdRxqK7T6XGugGbHIRl5Q7q3BssH6xk4s=
 google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:qbZzneIOXSq+KFAFut9krLfRLZiFLzZL5u2t8SV83EE=
-google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
-google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
+google.golang.org/genproto v0.0.0-20250313205543-e70fdf4c4cb4 h1:kCjWYliqPA8g5z87mbjnf/cdgQqMzBfp9xYre5qKu2A=
+google.golang.org/genproto v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:SqIx1NV9hcvqdLHo7uNZDS5lrUJybQ3evo3+z/WBfA0=
 google.golang.org/genproto/googleapis/api v0.0.0-20250207221924-e9438ea467c6 h1:L9JNMl/plZH9wmzQUHleO/ZZDSN+9Gh41wPczNy+5Fk=
 google.golang.org/genproto/googleapis/api v0.0.0-20250207221924-e9438ea467c6/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4=
-google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
-google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
+google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 h1:IFnXJq3UPB3oBREOodn1v1aGQeZYQclEmvWRMN0PSsY=
+google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:c8q6Z6OCqnfVIqUFJkCzKcrj8eCvUrz+K4KRzSTuANg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e h1:YA5lmSs3zc/5w+xsRcHqpETkaYyK63ivEPzNTcUUlSA=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
 google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
 google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
 google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
diff --git a/main.go b/main.go
index 8ce68b66..ee1141d9 100644
--- a/main.go
+++ b/main.go
@@ -54,6 +54,7 @@ var (
 	autoGenerateTitle           = os.Getenv("AUTO_GENERATE_TITLE")
 	autoGenerateTags            = os.Getenv("AUTO_GENERATE_TAGS")
 	autoGenerateCorrespondents  = os.Getenv("AUTO_GENERATE_CORRESPONDENTS")
+	autoGenerateCreatedDate     = os.Getenv("AUTO_GENERATE_CREATED_DATE")
 	limitOcrPages               int // Will be read from OCR_LIMIT_PAGES
 	tokenLimit                  = 0 // Will be read from TOKEN_LIMIT
 
@@ -61,6 +62,7 @@ var (
 	titleTemplate         *template.Template
 	tagTemplate           *template.Template
 	correspondentTemplate *template.Template
+	createdDateTemplate   *template.Template
 	ocrTemplate           *template.Template
 	templateMutex         sync.RWMutex
 
@@ -113,6 +115,13 @@ The content is likely in {{.Language}}.
 
 Document Content:
 {{.Content}}
+`
+	defaultCreatedDateTemplate = `I will provide you with the content of a document. Your task is to find the date when the document was created.
+Respond only with the date in YYYY-MM-DD format, without any additional information. If no date is found, respond with today's date.
+The content is likely in {{.Language}}.
+
+Content:
+{{.Content}}
 `
 	defaultOcrPrompt = `Just transcribe the text in this image and preserve the formatting and layout (high quality OCR). Do that for ALL the text in the image. Be thorough and pay attention. This is very important. The image is from a text document so be sure to continue until the bottom of the page. Thanks a lot! You tend to forget about some text in the image so please focus! Use markdown format but without a code block.`
 )
@@ -506,6 +515,7 @@ func (app *App) processAutoTagDocuments() (int, error) {
 			GenerateTitles:         strings.ToLower(autoGenerateTitle) != "false",
 			GenerateTags:           strings.ToLower(autoGenerateTags) != "false",
 			GenerateCorrespondents: strings.ToLower(autoGenerateCorrespondents) != "false",
+			GenerateCreatedDate:    strings.ToLower(autoGenerateCreatedDate) != "false",
 		}
 
 		suggestions, err := app.generateDocumentSuggestions(ctx, suggestionRequest, docLogger)
@@ -643,6 +653,22 @@ func loadTemplates() {
 		log.Fatalf("Failed to parse correspondent template: %v", err)
 	}
 
+	// Load createdDate template
+	createdDateTemplatePath := filepath.Join(promptsDir, "created_date_prompt.tmpl")
+	createdDateTemplateContent, err := os.ReadFile(createdDateTemplatePath)
+	if err != nil {
+		log.Errorf("Could not read %s, using default template: %v", createdDateTemplatePath, err)
+		createdDateTemplateContent = []byte(defaultCreatedDateTemplate)
+		if err := os.WriteFile(createdDateTemplatePath, createdDateTemplateContent, os.ModePerm); err != nil {
+			log.Fatalf("Failed to write default date template to disk: %v", err)
+		}
+	}
+
+	createdDateTemplate, err = template.New("created_date").Funcs(sprig.FuncMap()).Parse(string(createdDateTemplateContent))
+	if err != nil {
+		log.Fatalf("Failed to parse createdDate template: %v", err)
+	}
+
 	// Load OCR template
 	ocrTemplatePath := filepath.Join(promptsDir, "ocr_prompt.tmpl")
 	ocrTemplateContent, err := os.ReadFile(ocrTemplatePath)
diff --git a/main_test.go b/main_test.go
index cf4e3d1e..62372b52 100644
--- a/main_test.go
+++ b/main_test.go
@@ -28,6 +28,8 @@ func TestProcessAutoTagDocuments(t *testing.T) {
 	require.NoError(t, err)
 	correspondentTemplate, err = template.New("correspondent").Funcs(sprig.FuncMap()).Parse("")
 	require.NoError(t, err)
+	createdDateTemplate, err = template.New("created_date").Funcs(sprig.FuncMap()).Parse("")
+	require.NoError(t, err)
 
 	// Create test environment
 	env := newTestEnv(t)
@@ -104,10 +106,11 @@ func TestProcessAutoTagDocuments(t *testing.T) {
 						}
 					}
 					response.Results[i] = GetDocumentApiResponseResult{
-						ID:      doc.ID,
-						Title:   doc.Title,
-						Tags:    tagIds,
-						Content: "Test content",
+						ID:          doc.ID,
+						Title:       doc.Title,
+						Tags:        tagIds,
+						Content:     "Test content",
+						CreatedDate: "1999-09-09",
 					}
 				}
 				w.WriteHeader(http.StatusOK)
@@ -146,6 +149,7 @@ func TestProcessAutoTagDocuments(t *testing.T) {
 			autoGenerateTitle = "true"
 			autoGenerateTags = "true"
 			autoGenerateCorrespondents = "true"
+			autoGenerateCreatedDate = "true"
 
 			// Mock the document update responses
 			for _, doc := range tc.documents {
@@ -154,9 +158,10 @@ func TestProcessAutoTagDocuments(t *testing.T) {
 					env.setMockResponse(updatePath, func(w http.ResponseWriter, r *http.Request) {
 						w.WriteHeader(tc.updateResponse)
 						json.NewEncoder(w).Encode(map[string]interface{}{
-							"id":    doc.ID,
-							"title": "Updated " + doc.Title,
-							"tags":  []int{1, 3}, // Mock updated tag IDs
+							"id":           doc.ID,
+							"title":        "Updated " + doc.Title,
+							"tags":         []int{1, 3}, // Mock updated tag IDs
+							"created_date": "1999-09-19",
 						})
 					})
 				}
diff --git a/paperless.go b/paperless.go
index 2cf7c97b..b5deec02 100644
--- a/paperless.go
+++ b/paperless.go
@@ -92,8 +92,8 @@ func (client *PaperlessClient) Do(ctx context.Context, method, path string, body
 	}
 
 	log.WithFields(logrus.Fields{
-		"method":  method,
-		"url":     url,
+		"method": method,
+		"url":    url,
 	}).Debug("Making HTTP request")
 
 	resp, err := client.HTTPClient.Do(req)
@@ -276,6 +276,7 @@ func (client *PaperlessClient) GetDocumentsByTags(ctx context.Context, tags []st
 			Content:       result.Content,
 			Correspondent: correspondentName,
 			Tags:          tagNames,
+			CreatedDate:   result.CreatedDate,
 		})
 	}
 
@@ -354,6 +355,7 @@ func (client *PaperlessClient) GetDocument(ctx context.Context, documentID int)
 		Content:       documentResponse.Content,
 		Correspondent: correspondentName,
 		Tags:          tagNames,
+		CreatedDate:   documentResponse.CreatedDate,
 	}, nil
 }
 
@@ -472,6 +474,14 @@ func (client *PaperlessClient) UpdateDocuments(ctx context.Context, documents []
 			originalFields["content"] = document.OriginalDocument.Content
 			updatedFields["content"] = suggestedContent
 		}
+
+		// Suggested CreatedDate
+		suggestedCreatedDate := document.SuggestedCreatedDate
+		if suggestedCreatedDate != "" {
+			originalFields["created_date"] = document.OriginalDocument.CreatedDate
+			updatedFields["created_date"] = suggestedCreatedDate
+		}
+
 		log.Debugf("Document %d: Original fields: %v", documentID, originalFields)
 		log.Debugf("Document %d: Updated fields: %v Tags: %v", documentID, updatedFields, tags)
 
@@ -497,7 +507,7 @@ func (client *PaperlessClient) UpdateDocuments(ctx context.Context, documents []
 			return fmt.Errorf("error updating document %d: %d, %s", documentID, resp.StatusCode, string(bodyBytes))
 		} else {
 			for field, value := range originalFields {
-				log.Printf("Document %d: Updated %s from %v to %v", documentID, field, originalFields[field], value)
+				log.Printf("Document %d: Updated %s from %v to %v", documentID, field, value, updatedFields[field])
 				// Insert the modification record into the database
 				var modificationRecord ModificationHistory
 				if field == "tags" {
diff --git a/paperless_test.go b/paperless_test.go
index 482a825e..c7ef4f2e 100644
--- a/paperless_test.go
+++ b/paperless_test.go
@@ -186,6 +186,7 @@ func TestGetDocumentsByTags(t *testing.T) {
 				Content:       "Content 1",
 				Tags:          []int{1, 2},
 				Correspondent: 1,
+				CreatedDate:   "1999-09-01",
 			},
 			{
 				ID:            2,
@@ -193,6 +194,7 @@ func TestGetDocumentsByTags(t *testing.T) {
 				Content:       "Content 2",
 				Tags:          []int{2, 3},
 				Correspondent: 2,
+				CreatedDate:   "1999-09-02",
 			},
 		},
 	}
@@ -233,6 +235,7 @@ func TestGetDocumentsByTags(t *testing.T) {
 			Content:       "Content 1",
 			Tags:          []string{"tag1", "tag2"},
 			Correspondent: "Alpha",
+			CreatedDate:   "1999-09-01",
 		},
 		{
 			ID:            2,
@@ -240,6 +243,7 @@ func TestGetDocumentsByTags(t *testing.T) {
 			Content:       "Content 2",
 			Tags:          []string{"tag2", "tag3"},
 			Correspondent: "Beta",
+			CreatedDate:   "1999-09-02",
 		},
 	}
 
@@ -283,13 +287,15 @@ func TestUpdateDocuments(t *testing.T) {
 		{
 			ID: 1,
 			OriginalDocument: Document{
-				ID:    1,
-				Title: "Old Title",
-				Tags:  []string{"tag1", "tag3", "manual", "removeMe"},
+				ID:          1,
+				Title:       "Old Title",
+				Tags:        []string{"tag1", "tag3", "manual", "removeMe"},
+				CreatedDate: "1999-09-01",
 			},
-			SuggestedTitle: "New Title",
-			SuggestedTags:  []string{"tag2", "tag3"},
-			RemoveTags:     []string{"removeMe"},
+			SuggestedTitle:       "New Title",
+			SuggestedTags:        []string{"tag2", "tag3"},
+			RemoveTags:           []string{"removeMe"},
+			SuggestedCreatedDate: "1999-09-02",
 		},
 	}
 	idTag1 := 1
@@ -334,7 +340,8 @@ func TestUpdateDocuments(t *testing.T) {
 		expectedFields := map[string]interface{}{
 			"title": "New Title",
 			// do not keep previous tags since the tag generation will already take care to include old ones:
-			"tags": []interface{}{float64(idTag2), float64(idTag3)},
+			"tags":         []interface{}{float64(idTag2), float64(idTag3)},
+			"created_date": "1999-09-02",
 		}
 
 		assert.Equal(t, expectedFields, updatedFields)
diff --git a/types.go b/types.go
index 32163bf9..b47fd5c0 100644
--- a/types.go
+++ b/types.go
@@ -21,7 +21,7 @@ type GetDocumentApiResponseResult struct {
 	Content string `json:"content"`
 	Tags    []int  `json:"tags"`
 	// Created             time.Time     `json:"created"`
-	// CreatedDate         string        `json:"created_date"`
+	CreatedDate string `json:"created_date"`
 	// Modified            time.Time     `json:"modified"`
 	// Added               time.Time     `json:"added"`
 	// ArchiveSerialNumber interface{}   `json:"archive_serial_number"`
@@ -49,7 +49,7 @@ type GetDocumentApiResponse struct {
 	Content string `json:"content"`
 	Tags    []int  `json:"tags"`
 	// Created             time.Time     `json:"created"`
-	// CreatedDate         string        `json:"created_date"`
+	CreatedDate string `json:"created_date"`
 	// Modified            time.Time     `json:"modified"`
 	// Added               time.Time     `json:"added"`
 	// ArchiveSerialNumber interface{}   `json:"archive_serial_number"`
@@ -68,6 +68,7 @@ type Document struct {
 	Content       string   `json:"content"`
 	Tags          []string `json:"tags"`
 	Correspondent string   `json:"correspondent"`
+	CreatedDate   string   `json:"created_date"`
 }
 
 // GenerateSuggestionsRequest is the request payload for generating suggestions for /generate-suggestions endpoint
@@ -76,6 +77,7 @@ type GenerateSuggestionsRequest struct {
 	GenerateTitles         bool       `json:"generate_titles,omitempty"`
 	GenerateTags           bool       `json:"generate_tags,omitempty"`
 	GenerateCorrespondents bool       `json:"generate_correspondents,omitempty"`
+	GenerateCreatedDate    bool       `json:"generate_created_date,omitempty"`
 }
 
 // DocumentSuggestion is the response payload for /generate-suggestions endpoint and the request payload for /update-documents endpoint (as an array)
@@ -86,6 +88,7 @@ type DocumentSuggestion struct {
 	SuggestedTags          []string `json:"suggested_tags,omitempty"`
 	SuggestedContent       string   `json:"suggested_content,omitempty"`
 	SuggestedCorrespondent string   `json:"suggested_correspondent,omitempty"`
+	SuggestedCreatedDate   string   `json:"suggested_created_date,omitempty"`
 	RemoveTags             []string `json:"remove_tags,omitempty"`
 }
 
diff --git a/web-app/src/DocumentProcessor.tsx b/web-app/src/DocumentProcessor.tsx
index f8efd136..b64f51da 100644
--- a/web-app/src/DocumentProcessor.tsx
+++ b/web-app/src/DocumentProcessor.tsx
@@ -19,6 +19,7 @@ export interface GenerateSuggestionsRequest {
   generate_titles?: boolean;
   generate_tags?: boolean;
   generate_correspondents?: boolean;
+  generate_created_date?: boolean;
 }
 
 export interface DocumentSuggestion {
@@ -28,6 +29,7 @@ export interface DocumentSuggestion {
   suggested_tags?: string[];
   suggested_content?: string;
   suggested_correspondent?: string;
+  suggested_created_date?: string;
 }
 
 export interface TagOption {
@@ -47,6 +49,7 @@ const DocumentProcessor: React.FC = () => {
   const [generateTitles, setGenerateTitles] = useState(true);
   const [generateTags, setGenerateTags] = useState(true);
   const [generateCorrespondents, setGenerateCorrespondents] = useState(true);
+  const [generateCreatedDate, setGenerateCreatedDate] = useState(true);
   const [error, setError] = useState<string | null>(null);
 
   // Custom hook to fetch initial data
@@ -86,6 +89,7 @@ const DocumentProcessor: React.FC = () => {
         generate_titles: generateTitles,
         generate_tags: generateTags,
         generate_correspondents: generateCorrespondents,
+        generate_created_date: generateCreatedDate,
       };
 
       const { data } = await axios.post<DocumentSuggestion[]>(
@@ -159,6 +163,14 @@ const DocumentProcessor: React.FC = () => {
     );
   }
 
+  const handleCreatedDateChange = (docId: number, createdDate: string) => {
+    setSuggestions((prevSuggestions) =>
+      prevSuggestions.map((doc) =>
+        doc.id === docId ? { ...doc, suggested_created_date: createdDate } : doc
+      )
+    );
+  }
+
   const resetSuggestions = () => {
     setSuggestions([]);
   };
@@ -230,6 +242,8 @@ const DocumentProcessor: React.FC = () => {
           setGenerateTags={setGenerateTags}
           generateCorrespondents={generateCorrespondents}
           setGenerateCorrespondents={setGenerateCorrespondents}
+          generateCreatedDate={generateCreatedDate}
+          setGenerateCreatedDate={setGenerateCreatedDate}
           onProcess={handleProcessDocuments}
           processing={processing}
           onReload={reloadDocuments}
@@ -242,6 +256,7 @@ const DocumentProcessor: React.FC = () => {
           onTagAddition={handleTagAddition}
           onTagDeletion={handleTagDeletion}
           onCorrespondentChange={handleCorrespondentChange}
+          onCreatedDateChange={handleCreatedDateChange}
           onBack={resetSuggestions}
           onUpdate={handleUpdateDocuments}
           updating={updating}
diff --git a/web-app/src/components/DocumentsToProcess.tsx b/web-app/src/components/DocumentsToProcess.tsx
index acc1a11c..f6ca9c06 100644
--- a/web-app/src/components/DocumentsToProcess.tsx
+++ b/web-app/src/components/DocumentsToProcess.tsx
@@ -11,6 +11,8 @@ interface DocumentsToProcessProps {
   setGenerateTags: React.Dispatch<React.SetStateAction<boolean>>;
   generateCorrespondents: boolean;
   setGenerateCorrespondents: React.Dispatch<React.SetStateAction<boolean>>;
+  generateCreatedDate: boolean;
+  setGenerateCreatedDate: React.Dispatch<React.SetStateAction<boolean>>;
   onProcess: () => void;
   processing: boolean;
   onReload: () => void;
@@ -24,6 +26,8 @@ const DocumentsToProcess: React.FC<DocumentsToProcessProps> = ({
   setGenerateTags,
   generateCorrespondents,
   setGenerateCorrespondents,
+  generateCreatedDate,
+  setGenerateCreatedDate,
   onProcess,
   processing,
   onReload,
@@ -77,6 +81,15 @@ const DocumentsToProcess: React.FC<DocumentsToProcessProps> = ({
         />
         <span className="text-gray-700 dark:text-gray-200">Generate Correspondents</span>
       </label>
+      <label className="flex items-center space-x-2">
+        <input
+          type="checkbox"
+          checked={generateCreatedDate}
+          onChange={(e) => setGenerateCreatedDate(e.target.checked)}
+          className="dark:bg-gray-700 dark:border-gray-600"
+        />
+        <span className="text-gray-700 dark:text-gray-200">Generate Created Date</span>
+      </label>
     </div>
 
     <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
diff --git a/web-app/src/components/SuggestionCard.tsx b/web-app/src/components/SuggestionCard.tsx
index 6b7be1fb..b5a403cd 100644
--- a/web-app/src/components/SuggestionCard.tsx
+++ b/web-app/src/components/SuggestionCard.tsx
@@ -9,6 +9,7 @@ interface SuggestionCardProps {
   onTagAddition: (docId: number, tag: TagOption) => void;
   onTagDeletion: (docId: number, index: number) => void;
   onCorrespondentChange: (docId: number, correspondent: string) => void;
+  onCreatedDateChange: (docId: number, createdDate: string) => void;
 }
 
 const SuggestionCard: React.FC<SuggestionCardProps> = ({
@@ -18,6 +19,7 @@ const SuggestionCard: React.FC<SuggestionCardProps> = ({
   onTagAddition,
   onTagDeletion,
   onCorrespondentChange,
+  onCreatedDateChange,
 }) => {
   const sortedAvailableTags = availableTags.sort((a, b) => a.name.localeCompare(b.name));
   const document = suggestion.original_document;
@@ -119,6 +121,18 @@ const SuggestionCard: React.FC<SuggestionCardProps> = ({
             placeholder="Correspondent"
           />
         </div>
+        <div className="mt-4">
+          <label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
+            Suggested Created Date
+          </label>
+          <input
+            type="text"
+            value={suggestion.suggested_created_date || ""}
+            onChange={(e) => onCreatedDateChange(suggestion.id, e.target.value)}
+            className="w-full border border-gray-300 dark:border-gray-600 rounded px-2 py-1 mt-2 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-200"
+            placeholder="Created Date"
+          />
+        </div>
       </div>
     </div>
   );
diff --git a/web-app/src/components/SuggestionsReview.tsx b/web-app/src/components/SuggestionsReview.tsx
index c331c4b1..79ff70b1 100644
--- a/web-app/src/components/SuggestionsReview.tsx
+++ b/web-app/src/components/SuggestionsReview.tsx
@@ -9,6 +9,7 @@ interface SuggestionsReviewProps {
   onTagAddition: (docId: number, tag: TagOption) => void;
   onTagDeletion: (docId: number, index: number) => void;
   onCorrespondentChange: (docId: number, correspondent: string) => void;
+  onCreatedDateChange: (docId: number, createdDate: string) => void;
   onBack: () => void;
   onUpdate: () => void;
   updating: boolean;
@@ -21,6 +22,7 @@ const SuggestionsReview: React.FC<SuggestionsReviewProps> = ({
   onTagAddition,
   onTagDeletion,
   onCorrespondentChange,
+  onCreatedDateChange,
   onBack,
   onUpdate,
   updating,
@@ -39,6 +41,7 @@ const SuggestionsReview: React.FC<SuggestionsReviewProps> = ({
           onTagAddition={onTagAddition}
           onTagDeletion={onTagDeletion}
           onCorrespondentChange={onCorrespondentChange}
+          onCreatedDateChange={onCreatedDateChange}
         />
       ))}
     </div>
diff --git a/web-app/tsconfig.app.tsbuildinfo b/web-app/tsconfig.app.tsbuildinfo
index 53418626..ac1012f2 100644
--- a/web-app/tsconfig.app.tsbuildinfo
+++ b/web-app/tsconfig.app.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/app.tsx","./src/documentprocessor.tsx","./src/experimentalocr.tsx","./src/history.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/documentcard.tsx","./src/components/documentstoprocess.tsx","./src/components/nodocuments.tsx","./src/components/sidebar.tsx","./src/components/successmodal.tsx","./src/components/suggestioncard.tsx","./src/components/suggestionsreview.tsx","./src/components/undocard.tsx"],"version":"5.7.3"}
\ No newline at end of file
+{"root":["./src/App.tsx","./src/DocumentProcessor.tsx","./src/ExperimentalOCR.tsx","./src/History.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/DocumentCard.tsx","./src/components/DocumentsToProcess.tsx","./src/components/NoDocuments.tsx","./src/components/Sidebar.tsx","./src/components/SuccessModal.tsx","./src/components/SuggestionCard.tsx","./src/components/SuggestionsReview.tsx","./src/components/UndoCard.tsx"],"version":"5.7.3"}
\ No newline at end of file


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>README.md (1)</summary><blockquote>

`295-295`: **Fix the punctuation in the documentation.**

There appears to be a loose punctuation mark at the end of the sentence.

```diff
-5. **`created_date_prompt.tmpl`**: For setting of document's created date.
+5. **`created_date_prompt.tmpl`**: For setting the document's created date.
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[uncategorized] ~295-~295: Loose punctuation mark.
Context: ...ation. 5. **`created_date_prompt.tmpl`**: For setting of document's created date....

(UNLIKELY_OPENING_PUNCTUATION)

</details>

</details>

</blockquote></details>
<details>
<summary>app_llm.go (1)</summary><blockquote>

`281-334`: **Well-implemented date generation method following existing patterns.**

The `getSuggestedCreatedDate` method effectively handles token limits, content truncation, and template execution in a manner consistent with other similar methods in the codebase.

There's one improvement to consider: adding validation for the date format returned by the LLM to ensure it's in the expected YYYY-MM-DD format.



Consider adding validation for the returned date format:

```diff
func (app *App) getSuggestedCreatedDate(ctx context.Context, content string, logger *logrus.Entry) (string, error) {
    // existing code...
    
    result := stripReasoning(completion.Choices[0].Content)
    trimmedResult := strings.TrimSpace(strings.Trim(result, "\""))
    
+   // Validate date format (YYYY-MM-DD)
+   if matched, _ := regexp.MatchString(`^\d{4}-\d{2}-\d{2}$`, trimmedResult); !matched {
+       logger.Warnf("LLM returned invalid date format: %s, using today's date", trimmedResult)
+       return time.Now().Format("2006-01-02"), nil
+   }
    
-   return strings.TrimSpace(strings.Trim(result, "\"")), nil
+   return trimmedResult, nil
}
```

</blockquote></details>
<details>
<summary>web-app/src/components/SuggestionCard.tsx (1)</summary><blockquote>

`124-135`: **New UI element for suggested created date.**

The implementation for the created date input field follows the existing pattern used for other fields, maintaining consistency in the UI.

However, consider using a date input type instead of text for better user experience.

```diff
- <input
-   type="text"
-   value={suggestion.suggested_created_date || ""}
-   onChange={(e) => onCreatedDateChange(suggestion.id, e.target.value)}
-   className="w-full border border-gray-300 dark:border-gray-600 rounded px-2 py-1 mt-2 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-200"
-   placeholder="Created Date"
- />
+ <input
+   type="date"
+   value={suggestion.suggested_created_date || ""}
+   onChange={(e) => onCreatedDateChange(suggestion.id, e.target.value)}
+   className="w-full border border-gray-300 dark:border-gray-600 rounded px-2 py-1 mt-2 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-200"
+   placeholder="Created Date"
+ />
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between e3deb9b17e09f31862cd3276f18a901c4e1458e7 and 605648d37fe2aab256cd3f182b974f323c48507c.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `go.sum` is excluded by `!**/*.sum`

</details>

<details>
<summary>📒 Files selected for processing (14)</summary>

* `README.md` (4 hunks)
* `app_llm.go` (4 hunks)
* `app_llm_test.go` (3 hunks)
* `go.mod` (2 hunks)
* `main.go` (4 hunks)
* `main_test.go` (4 hunks)
* `paperless.go` (5 hunks)
* `paperless_test.go` (4 hunks)
* `types.go` (5 hunks)
* `web-app/src/DocumentProcessor.tsx` (7 hunks)
* `web-app/src/components/DocumentsToProcess.tsx` (3 hunks)
* `web-app/src/components/SuggestionCard.tsx` (3 hunks)
* `web-app/src/components/SuggestionsReview.tsx` (3 hunks)
* `web-app/tsconfig.app.tsbuildinfo` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (2)</summary>

<details>
<summary>app_llm_test.go (3)</summary><blockquote>

<details>
<summary>main.go (7) (7)</summary>

* `createdDateTemplate` (65-65)
* `err` (223-223)
* `t` (765-770)
* `app` (410-412)
* `app` (487-535)
* `app` (538-578)
* `App` (130-136)

</details>
<details>
<summary>app_llm.go (6) (6)</summary>

* `app` (20-72)
* `app` (75-165)
* `app` (167-223)
* `app` (226-279)
* `app` (282-334)
* `app` (337-482)

</details>
<details>
<summary>tokens_test.go (1) (1)</summary>

* `resetTokenLimit` (17-26)

</details>

</blockquote></details>
<details>
<summary>app_llm.go (2)</summary><blockquote>

<details>
<summary>main.go (7) (7)</summary>

* `app` (410-412)
* `app` (487-535)
* `app` (538-578)
* `App` (130-136)
* `getLikelyLanguage` (592-598)
* `err` (223-223)
* `log` (32-32)

</details>
<details>
<summary>tokens.go (3) (3)</summary>

* `getAvailableTokensForContent` (13-47)
* `truncateContentByTokens` (56-99)
* `promptBuffer` (26-26)

</details>

</blockquote></details>

</details><details>
<summary>🪛 golangci-lint (1.64.8)</summary>

<details>
<summary>app_llm_test.go</summary>

75-75: ineffectual assignment to err

(ineffassign)

</details>
<details>
<summary>main_test.go</summary>

31-31: ineffectual assignment to err

(ineffassign)

</details>

</details>
<details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>README.md</summary>

[uncategorized] ~295-~295: Loose punctuation mark.
Context: ...ation. 5. **`created_date_prompt.tmpl`**: For setting of document's created date....

(UNLIKELY_OPENING_PUNCTUATION)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (45)</summary><blockquote>

<details>
<summary>main_test.go (3)</summary>

`112-112`: **LGTM: Added CreatedDate field to document response**

The addition of the CreatedDate field aligns with the new feature for generated dates.

---

`151-151`: **LGTM: Added auto-generate flag for created date**

The addition of the autoGenerateCreatedDate flag follows the pattern of other auto-generate flags and enables testing of the new feature.

---

`163-163`: **LGTM: Added created_date to mock response**

The addition of the created_date field to the mock response JSON ensures proper testing of the created date update functionality.

</details>
<details>
<summary>paperless_test.go (4)</summary>

`189-190`: **LGTM: Added CreatedDate field to test documents**

The CreatedDate field has been appropriately added to the test document data structures with consistent test values.



Also applies to: 197-198

---

`238-239`: **LGTM: Added CreatedDate to OriginalDocument in test**

The CreatedDate field has been properly added to the OriginalDocument struct in the test data.



Also applies to: 293-293

---

`298-298`: **LGTM: Added SuggestedCreatedDate field to document suggestion**

The SuggestedCreatedDate field has been appropriately added to the document suggestion test data.

---

`344-345`: **LGTM: Added created_date to expected updated fields**

The created_date field has been properly added to the expected updated fields in the test, ensuring that the update functionality is correctly tested.

</details>
<details>
<summary>app_llm_test.go (2)</summary>

`58-61`: **LGTM: Added test template for created date**

The test template for created date follows the established pattern of other test templates.

---

`275-309`: **LGTM: Added test for token limit in created date generation**

The test for token limit in created date generation follows the same pattern as other token limit tests and ensures that the created date generation respects token limits.

</details>
<details>
<summary>types.go (3)</summary>

`24-24`: **LGTM: Added CreatedDate field to document structs**

The CreatedDate field has been appropriately added to the relevant document structs (GetDocumentApiResponseResult, GetDocumentApiResponse, Document) with consistent JSON tags.



Also applies to: 52-52, 71-71

---

`80-80`: **LGTM: Added GenerateCreatedDate flag to request struct**

The GenerateCreatedDate boolean flag has been properly added to the GenerateSuggestionsRequest struct with an appropriate omitempty tag.

---

`91-91`: **LGTM: Added SuggestedCreatedDate field to DocumentSuggestion**

The SuggestedCreatedDate field has been appropriately added to the DocumentSuggestion struct with a consistent naming pattern and omitempty tag.

</details>
<details>
<summary>main.go (5)</summary>

`57-57`: **Well implemented environment variable for created date generation.**

The new `autoGenerateCreatedDate` environment variable follows the consistent naming pattern of other auto-generate variables in the application.

---

`65-65`: **Good addition of template variable.**

The `createdDateTemplate` variable is properly declared alongside other template variables, maintaining code organization.

---

`119-125`: **Prompt template looks good and follows the established pattern.**

The default template for created date is well-structured and consistent with other templates, providing clear instructions to the LLM on how to format the response.

---

`518-518`: **Correct implementation of the `GenerateCreatedDate` flag.**

The flag is properly implemented with the same pattern as other generation flags, converting the environment variable to a boolean value.

---

`656-670`: **Template loading implementation is consistent and robust.**

The implementation for loading the created date template follows the same pattern as other templates, with proper error handling and fallback to the default template.

</details>
<details>
<summary>README.md (3)</summary>

`27-27`: **Documentation title updated to reflect new functionality.**

The section title now correctly includes the new created date generation functionality.

---

`282-282`: **Environment variable documentation is clear and consistent.**

The `AUTO_GENERATE_CREATED_DATE` environment variable is well documented with a clear description, default value, and optional status.

---

`332-334`: **Template variables documentation is clear and comprehensive.**

The documentation for the created date template variables is complete and follows the same format as other template variables.

</details>
<details>
<summary>app_llm.go (3)</summary>

`384-384`: **Good addition of the suggested created date variable.**

The variable is properly introduced and initialized at the right scope level.

---

`419-428`: **Created date generation logic is well-implemented.**

The implementation follows the same pattern as title, tags, and correspondent generation with proper error handling and logging.

---

`459-465`: **Proper handling of suggested created date in document suggestions.**

The suggested created date is correctly handled in the document suggestions structure, following the same pattern as other suggested fields.

</details>
<details>
<summary>paperless.go (4)</summary>

`279-279`: **Good addition of the CreatedDate field to Document struct.**

The `CreatedDate` field is properly added to the `Document` struct in the `GetDocumentsByTags` method.

---

`358-358`: **Consistent integration of CreatedDate field in GetDocument method.**

The `CreatedDate` field is properly added to the `Document` struct in the `GetDocument` method, maintaining consistency with the `GetDocumentsByTags` method.

---

`478-483`: **Good implementation of suggested created date handling.**

The implementation for handling the suggested created date in the `UpdateDocuments` method follows the same pattern as other suggested fields, with proper field mapping and update logic.

---

`510-510`: **Corrected logging format to use updated fields.**

The logging statement now correctly uses the `updatedFields` map to show the new values.

</details>
<details>
<summary>go.mod (2)</summary>

`20-20`: **Version upgrade for google.golang.org/api looks good.**

The upgrade from an older version to v0.226.0 aligns with the dependency updates for this project.

---

`26-26`: <details>
<summary>❓ Verification inconclusive</summary>

**Dependency updates look appropriate.**

These dependency updates appear to be routine maintenance. Multiple libraries have been updated to newer versions, which is good practice for security and feature improvements.






Also applies to: 30-30, 33-40, 46-48, 58-58, 60-60, 67-68, 71-71, 80-85, 92-94

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if any of these dependency updates have known security issues
echo "Checking for potential security issues in updated dependencies..."

# Check google.golang.org/api
curl -s "https://pkg.go.dev/google.golang.org/api@v0.226.0?tab=versions" | grep -A 2 "Latest"

# Check cloud.google.com/go
curl -s "https://pkg.go.dev/cloud.google.com/go@v0.119.0?tab=versions" | grep -A 2 "Latest"

# Check bytedance/sonic
curl -s "https://pkg.go.dev/github.com/bytedance/sonic@v1.13.1?tab=versions" | grep -A 2 "Latest"
```

Length of output: 424

---



</details>

**Dependencies Updated – Manual Verification Required**

The dependency updates in your `go.mod` file appear routine and well-maintained. However, our automated script for checking potential security issues returned no useful output. Please manually verify that the updates (e.g., for `google.golang.org/api` v0.226.0, `cloud.google.com/go` v0.119.0, and `bytedance/sonic` v1.13.1) do not introduce new vulnerabilities.

- **Line 26 (and similar updates at 30, 33–40, 46–48, 58, 60, 67–68, 71, 80–85, 92–94):** Dependency versions are updated.
- **Next Steps:** Confirm on pkg.go.dev or other trusted sources that these versions are secure and free of known issues.

</details>
<details>
<summary>web-app/src/components/SuggestionsReview.tsx (3)</summary>

`12-12`: **Added interface property for created date handling.**

The new `onCreatedDateChange` property appropriately defines a callback function to handle created date changes, with the proper types for document ID and date value.

---

`25-25`: **Included onCreatedDateChange in component properties.**

The created date change handler is correctly added to the component's destructuring assignment, making it available within the component.

---

`44-44`: **Properly passed the handler to child component.**

The `onCreatedDateChange` handler is correctly passed down to the `SuggestionCard` component, enabling each card to handle created date modifications.

</details>
<details>
<summary>web-app/src/components/SuggestionCard.tsx (2)</summary>

`12-12`: **Added interface property for created date changes.**

The new property correctly defines the function signature for handling created date changes.

---

`22-22`: **Included onCreatedDateChange in component properties.**

The created date change handler is correctly added to the component's destructuring assignment.

</details>
<details>
<summary>web-app/tsconfig.app.tsbuildinfo (1)</summary>

`1-1`: **Updated file paths with consistent PascalCase naming.**

The file naming convention has been updated from lowercase to PascalCase, which improves consistency across the codebase. This is a good practice for React component files.

</details>
<details>
<summary>web-app/src/components/DocumentsToProcess.tsx (3)</summary>

`14-15`: **LGTM: Interface properly extended with created date properties**

The `DocumentsToProcessProps` interface has been properly extended with the new properties for created date generation, following the same pattern as the existing properties for titles, tags, and correspondents.

---

`29-30`: **LGTM: Component parameters correctly updated**

The component's destructuring pattern has been updated to include the new `generateCreatedDate` and `setGenerateCreatedDate` props, maintaining consistency with the existing pattern.

---

`84-92`: **LGTM: Created date checkbox implementation**

The new checkbox for "Generate Created Date" follows the same pattern and styling as the existing checkboxes, ensuring a consistent user experience. The implementation correctly binds the checkbox state to the `generateCreatedDate` prop and handles changes with the `setGenerateCreatedDate` function.

</details>
<details>
<summary>web-app/src/DocumentProcessor.tsx (7)</summary>

`22-22`: **LGTM: Request interface properly extended**

The `GenerateSuggestionsRequest` interface has been correctly extended with an optional `generate_created_date` property, following the same pattern as the existing generation options.

---

`32-32`: **LGTM: Suggestion interface properly extended**

The `DocumentSuggestion` interface has been correctly extended with an optional `suggested_created_date` property, maintaining consistency with the existing suggestion properties.

---

`52-52`: **LGTM: State initialization for created date**

The new state variable `generateCreatedDate` is initialized to `true` by default, which aligns with the PR objectives and follows the pattern of other generation options.

---

`92-92`: **LGTM: Request payload properly includes created date option**

The created date generation option is correctly included in the request payload, ensuring the backend will receive the user's preference.

---

`166-172`: **LGTM: Handler function implemented correctly**

The `handleCreatedDateChange` function follows the same pattern as other handler functions, properly updating the specific document's suggested created date in the state.

---

`245-246`: **LGTM: Props correctly passed to DocumentsToProcess**

The created date state and setter are correctly passed to the `DocumentsToProcess` component, enabling the checkbox functionality.

---

`259-259`: **LGTM: Handler function correctly passed to SuggestionsReview**

The created date change handler is correctly passed to the `SuggestionsReview` component, enabling the editing of suggested created dates.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T19:39:39Z
user: login: AxcellF
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-22T19:42:59Z

Review comments:
diff_hunk: @@ -28,6 +28,7 @@ func TestProcessAutoTagDocuments(t *testing.T) {
 	require.NoError(t, err)
 	correspondentTemplate, err = template.New("correspondent").Funcs(sprig.FuncMap()).Parse("")
 	require.NoError(t, err)
+	createdDateTemplate, err = template.New("created_date").Funcs(sprig.FuncMap()).Parse("")
path: main_test.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Check the error return value from template initialization**

The error returned from `template.New("created_date").Funcs(sprig.FuncMap()).Parse("")` is not being checked. This could lead to silent failures if there's an issue with the template initialization.

Modify the code to check the error value similar to other template initializations:

```diff
createdDateTemplate, err = template.New("created_date").Funcs(sprig.FuncMap()).Parse("")
+require.NoError(t, err)
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	createdDateTemplate, err = template.New("created_date").Funcs(sprig.FuncMap()).Parse("")
	require.NoError(t, err)
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 golangci-lint (1.64.8)</summary>

31-31: ineffectual assignment to err

(ineffassign)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 568c801
created_at: 2025-03-22T19:39:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -68,6 +72,7 @@ func TestPromptTokenLimits(t *testing.T) {
 	require.NoError(t, err)
 	correspondentTemplate, err = template.New("correspondent").Parse(testCorrespondentTemplate)
 	require.NoError(t, err)
+	createdDateTemplate, err = template.New("created_date").Parse(testCreatedDateContentTemplate)
path: app_llm_test.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Check the error return value from template initialization**

The error returned from `template.New("created_date").Parse(testCreatedDateContentTemplate)` is not being checked. This could lead to silent failures if there's an issue with the template initialization.

Modify the code to check the error value similar to other template initializations:

```diff
createdDateTemplate, err = template.New("created_date").Parse(testCreatedDateContentTemplate)
+require.NoError(t, err)
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	createdDateTemplate, err = template.New("created_date").Parse(testCreatedDateContentTemplate)
	require.NoError(t, err)
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 golangci-lint (1.64.8)</summary>

75-75: ineffectual assignment to err

(ineffassign)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 568c801
created_at: 2025-03-22T19:39:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -497,7 +507,7 @@ func (client *PaperlessClient) UpdateDocuments(ctx context.Context, documents []
 			return fmt.Errorf("error updating document %d: %d, %s", documentID, resp.StatusCode, string(bodyBytes))
 		} else {
 			for field, value := range originalFields {
-				log.Printf("Document %d: Updated %s from %v to %v", documentID, field, originalFields[field], value)
+				log.Printf("Document %d: Updated %s from %v to %v", documentID, field, value, updatedFields[field])
path: paperless.go
user: login: AxcellF
body: Forgot to mention this one in the PR. Looked like a small bug here previously, `originalFields[field]` and `value` are the same thing, so the log would say:
_Updated [document field] from X to X_ 
Rather than (what I assume was the intended):
_Updated [document field] from X to Y_. 
created_at: 2025-03-22T19:42:58Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-22T19:35:07Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This pull request integrates automatic document creation date generation into the system. It updates documentation, backend logic, tests, and user interfaces to support new fields, configuration variables, and prompt templates for created dates. A new LLM method suggests creation dates, and related data structures, API responses, and logging now include creation date metadata. Additionally, the update incorporates dependency upgrades and minor file path casing adjustments.

## Changes

| File(s)                                                                                      | Change Summary |
|----------------------------------------------------------------------------------------------|----------------|
| README.md                                                                                    | Updated documentation for automatic title, tag, and created date generation; added `AUTO_GENERATE_CREATED_DATE` env var and a new prompt template (`created_date_prompt.tmpl`) with additional variables. |
| app_llm.go, app_llm_test.go                                                                   | Added a new method `getSuggestedCreatedDate` to generate creation date suggestions via LLM; introduced tests for token limits and created date prompt processing. |
| main.go, main_test.go                                                                         | Introduced `autoGenerateCreatedDate` env var and `createdDateTemplate`; updated suggestion logic and test responses to include created date handling. |
| paperless.go, paperless_test.go, types.go                                                     | Enhanced document metadata by adding `CreatedDate` to structs and API responses; updated update methods to handle `SuggestedCreatedDate`; extended data structures with new fields. |
| go.mod                                                                                        | Upgraded multiple dependency versions. |
| web-app/src/DocumentProcessor.tsx, web-app/src/components/DocumentsToProcess.tsx, web-app/src/components/SuggestionCard.tsx, web-app/src/components/SuggestionsReview.tsx | Added UI support for creation date management through new properties, state management, and input handling for user customization. |
| web-app/tsconfig.app.tsbuildinfo                                                              | Adjusted file path casing to PascalCase. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    actor User
    participant Server as Main Process (main.go)
    participant Generator as Created Date Generator (getSuggestedCreatedDate)
    participant LLM as Language Model
    User->>Server: Submit document for processing
    Server->>Generator: Request created date suggestion (if enabled)
    Generator->>LLM: Generate prompt using created date template
    LLM-->>Generator: Return generated created date
    Generator-->>Server: Provide suggested created date
    Server->>User: Return document suggestions with created date
```

## Possibly related PRs
- icereed/paperless-gpt#60: The changes in the main PR regarding the automatic generation of created dates are related to the retrieved PR's introduction of environment variables for controlling the automatic generation of titles and tags, as both sets of changes enhance the document processing capabilities through similar configuration mechanisms.

## Poem
> I hop through lines of code with glee,  
> Crafting dates as bright as can be.  
> With prompts and tokens I dance and play,  
> Auto dates now join the fray.  
> A rabbit’s cheer for every update sprung—  
> Code and magic, forever young! 🐇

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 605648d37fe2aab256cd3f182b974f323c48507c and 568c8014ee574560be7d5f8ab3fef2323b2484da.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app_llm_test.go` (3 hunks)
* `main_test.go` (4 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* main_test.go

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Definitions (1)</summary>

<details>
<summary>app_llm_test.go (3)</summary><blockquote>

<details>
<summary>main.go (7) (7)</summary>

* `createdDateTemplate` (65-65)
* `err` (223-223)
* `t` (765-770)
* `app` (410-412)
* `app` (487-535)
* `app` (538-578)
* `App` (130-136)

</details>
<details>
<summary>app_llm.go (6) (6)</summary>

* `app` (20-72)
* `app` (75-165)
* `app` (167-223)
* `app` (226-279)
* `app` (282-334)
* `app` (337-482)

</details>
<details>
<summary>tokens_test.go (1) (1)</summary>

* `resetTokenLimit` (17-26)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>app_llm_test.go (3)</summary>

`58-60`: **LGTM: Created date template follows established pattern.**

The new template for created date content follows the same structure as other templates, providing placeholders for language and content.

---

`75-76`: **Error handling properly implemented.**

The error returned from template initialization is now being checked, addressing the previous review comment. This prevents silent failures if there's an issue with template parsing.

---

`276-310`: **LGTM: Created date generation test is well-structured.**

The new test function follows the established pattern of other token limit tests in the file. It correctly:
- Sets up and restores environment variables
- Creates a mock LLM instance
- Tests that the token limit is respected by the `getSuggestedCreatedDate` function
- Verifies no errors occur during processing
- Ensures the final prompt size is within the configured token limit

This completes the test coverage for created date generation functionality.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=icereed/paperless-gpt&utm_content=303):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: icereed
created_at: 2025-03-23T07:47:31Z
body: Merged into a temporary branch to meet the tests run
author_association: OWNER