Enhance attachment manager with dual text content handling
- Updated `GetText` and `SaveText` methods to support both full content and a preview (first 2000 characters) for improved performance and usability. - Added comprehensive tests in `manager_test.go` to validate the new functionality, including UTF-8 character handling and content retrieval scenarios. - Modified the attachment model to include a `content_preview` field alongside the existing `content` field, ensuring efficient data management. - Enhanced the README.md to document the new dual storage feature, providing clear examples for users on how to utilize the updated methods.
This commit is contained in:
parent
4170fbd13b
commit
1060c0f0e0
6 changed files with 415 additions and 172 deletions
|
|
@ -663,9 +663,13 @@ case "upload_failed":
|
||||||
|
|
||||||
The attachment package supports storing parsed text content extracted from files (e.g., from PDFs, Word documents, or image OCR). This is useful for building search indexes or providing text-based previews.
|
The attachment package supports storing parsed text content extracted from files (e.g., from PDFs, Word documents, or image OCR). This is useful for building search indexes or providing text-based previews.
|
||||||
|
|
||||||
|
The system automatically maintains two versions of the text content:
|
||||||
|
- **Full content** (`content`): Complete text, stored as longText (up to 4GB)
|
||||||
|
- **Preview** (`content_preview`): First 2000 characters, stored as text for quick access
|
||||||
|
|
||||||
### Saving Parsed Text Content
|
### Saving Parsed Text Content
|
||||||
|
|
||||||
Use `SaveText` to store the extracted text content for a file:
|
Use `SaveText` to store the extracted text content. It automatically saves both full content and preview:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Upload a PDF file
|
// Upload a PDF file
|
||||||
|
|
@ -677,7 +681,7 @@ if err != nil {
|
||||||
// Extract text from the PDF (using your preferred library)
|
// Extract text from the PDF (using your preferred library)
|
||||||
parsedText := extractTextFromPDF(file.ID)
|
parsedText := extractTextFromPDF(file.ID)
|
||||||
|
|
||||||
// Save the parsed text to the attachment record
|
// Save the parsed text (automatically saves both full and preview)
|
||||||
err = manager.SaveText(ctx, file.ID, parsedText)
|
err = manager.SaveText(ctx, file.ID, parsedText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to save text content: %w", err)
|
return fmt.Errorf("failed to save text content: %w", err)
|
||||||
|
|
@ -686,22 +690,45 @@ if err != nil {
|
||||||
|
|
||||||
### Retrieving Parsed Text Content
|
### Retrieving Parsed Text Content
|
||||||
|
|
||||||
Use `GetText` to retrieve the stored text content:
|
Use `GetText` to retrieve text content. By default, it returns the preview for better performance:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Get the parsed text content
|
// Get preview (first 2000 characters) - Fast, suitable for UI display
|
||||||
text, err := manager.GetText(ctx, file.ID)
|
preview, err := manager.GetText(ctx, file.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get text content: %w", err)
|
return fmt.Errorf("failed to get preview: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if text == "" {
|
if preview == "" {
|
||||||
fmt.Println("No text content available for this file")
|
fmt.Println("No text content available for this file")
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Text content (%d characters): %s\n", len(text), text)
|
fmt.Printf("Preview (%d characters): %s\n", len(preview), preview)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get full content - Use only when complete text is needed (e.g., for indexing)
|
||||||
|
fullText, err := manager.GetText(ctx, file.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get full text: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Full content (%d characters)\n", len(fullText))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
|
||||||
|
The text content fields are optimized for different use cases:
|
||||||
|
|
||||||
|
| Field | Size Limit | Use Case | Performance |
|
||||||
|
|-------|------------|----------|-------------|
|
||||||
|
| `content_preview` | 2000 chars | Quick preview, UI display, snippets | ⚡ Very Fast |
|
||||||
|
| `content` | 4GB | Full text search, complete content | 🐌 Slow for large files |
|
||||||
|
|
||||||
|
**Best Practices:**
|
||||||
|
1. Use preview by default: `GetText(ctx, fileID)`
|
||||||
|
2. Only request full content when necessary: `GetText(ctx, fileID, true)`
|
||||||
|
3. Both fields are excluded from `List()` by default for optimal performance
|
||||||
|
4. Preview uses character (rune) count, not bytes, for proper UTF-8 handling
|
||||||
|
|
||||||
### Example: Complete Text Processing Workflow
|
### Example: Complete Text Processing Workflow
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
@ -750,11 +777,15 @@ fmt.Printf("Retrieved text: %s\n", savedText)
|
||||||
|
|
||||||
### Text Content Features
|
### Text Content Features
|
||||||
|
|
||||||
- **Storage**: Text content is stored in the `content` field (longText type) of the attachment record
|
- **Dual Storage**: Automatically maintains both full content and preview (2000 chars)
|
||||||
- **Size**: Supports very large text content (up to 4GB with longText type)
|
- **Size Limits**:
|
||||||
|
- Preview: 2000 characters (text type)
|
||||||
|
- Full content: Up to 4GB (longText type)
|
||||||
|
- **Smart Retrieval**: Returns preview by default, full content on demand
|
||||||
- **Update**: Text content can be updated at any time using `SaveText`
|
- **Update**: Text content can be updated at any time using `SaveText`
|
||||||
- **Clear**: Set text to empty string to clear the content
|
- **Clear**: Set text to empty string to clear both fields
|
||||||
- **Retrieval**: Returns empty string if no text content has been saved
|
- **UTF-8 Safe**: Preview uses character (rune) count, not bytes, ensuring proper multi-byte character handling
|
||||||
|
- **Performance**: Both `content` and `content_preview` fields are excluded by default in `List()` and `Info()` operations to avoid loading text data. Use `GetText()` to explicitly retrieve text content when needed
|
||||||
|
|
||||||
#### `RegisterDefault(name string) (*Manager, error)`
|
#### `RegisterDefault(name string) (*Manager, error)`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -723,6 +723,16 @@ func (manager Manager) List(ctx context.Context, option ListOption) (*ListResult
|
||||||
for _, field := range option.Select {
|
for _, field := range option.Select {
|
||||||
queryParam.Select = append(queryParam.Select, field)
|
queryParam.Select = append(queryParam.Select, field)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Default: exclude the 'content' field (which may contain large text data)
|
||||||
|
// Only include it if explicitly requested in Select
|
||||||
|
queryParam.Select = []interface{}{
|
||||||
|
"id", "file_id", "uploader", "content_type", "name", "url", "description",
|
||||||
|
"type", "user_path", "path", "groups", "gzip", "bytes", "status",
|
||||||
|
"progress", "error", "preset", "public", "share",
|
||||||
|
"created_at", "updated_at", "deleted_at",
|
||||||
|
"__yao_created_by", "__yao_updated_by", "__yao_team_id", "__yao_tenant_id",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add filters
|
// Add filters
|
||||||
|
|
@ -1324,12 +1334,24 @@ func (manager Manager) getStoragePathFromDatabase(ctx context.Context, fileID st
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetText retrieves the parsed text content for a file by its ID
|
// GetText retrieves the parsed text content for a file by its ID
|
||||||
// Returns the text content stored in the 'content' field of the attachment
|
// By default, returns the preview (first 2000 characters) from 'content_preview' field
|
||||||
func (manager Manager) GetText(ctx context.Context, fileID string) (string, error) {
|
// Set fullContent to true to retrieve the complete text from 'content' field
|
||||||
|
func (manager Manager) GetText(ctx context.Context, fileID string, fullContent ...bool) (string, error) {
|
||||||
m := model.Select("__yao.attachment")
|
m := model.Select("__yao.attachment")
|
||||||
|
|
||||||
|
// Determine which field to query
|
||||||
|
wantFullContent := false
|
||||||
|
if len(fullContent) > 0 {
|
||||||
|
wantFullContent = fullContent[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldName := "content_preview"
|
||||||
|
if wantFullContent {
|
||||||
|
fieldName = "content"
|
||||||
|
}
|
||||||
|
|
||||||
records, err := m.Get(model.QueryParam{
|
records, err := m.Get(model.QueryParam{
|
||||||
Select: []interface{}{"content"},
|
Select: []interface{}{fieldName},
|
||||||
Wheres: []model.QueryWhere{
|
Wheres: []model.QueryWhere{
|
||||||
{Column: "file_id", Value: fileID},
|
{Column: "file_id", Value: fileID},
|
||||||
},
|
},
|
||||||
|
|
@ -1345,7 +1367,7 @@ func (manager Manager) GetText(ctx context.Context, fileID string) (string, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle content field - it may be nil, string, or other types
|
// Handle content field - it may be nil, string, or other types
|
||||||
if content, ok := records[0]["content"].(string); ok {
|
if content, ok := records[0][fieldName].(string); ok {
|
||||||
return content, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1354,7 +1376,8 @@ func (manager Manager) GetText(ctx context.Context, fileID string) (string, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveText saves the parsed text content for a file by its ID
|
// SaveText saves the parsed text content for a file by its ID
|
||||||
// Updates the 'content' field in the attachment record
|
// Automatically saves both full content and preview (first 2000 characters)
|
||||||
|
// Updates both 'content' and 'content_preview' fields in the attachment record
|
||||||
func (manager Manager) SaveText(ctx context.Context, fileID string, text string) error {
|
func (manager Manager) SaveText(ctx context.Context, fileID string, text string) error {
|
||||||
m := model.Select("__yao.attachment")
|
m := model.Select("__yao.attachment")
|
||||||
|
|
||||||
|
|
@ -1375,9 +1398,17 @@ func (manager Manager) SaveText(ctx context.Context, fileID string, text string)
|
||||||
return fmt.Errorf("file not found: %s", fileID)
|
return fmt.Errorf("file not found: %s", fileID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the content field
|
// Create preview: first 2000 characters (or runes for proper UTF-8 handling)
|
||||||
|
preview := text
|
||||||
|
const maxPreviewLength = 2000
|
||||||
|
if len([]rune(text)) > maxPreviewLength {
|
||||||
|
preview = string([]rune(text)[:maxPreviewLength])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update both content and content_preview fields
|
||||||
updateData := map[string]interface{}{
|
updateData := map[string]interface{}{
|
||||||
"content": text,
|
"content": text,
|
||||||
|
"content_preview": preview,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = m.UpdateWhere(model.QueryParam{
|
_, err = m.UpdateWhere(model.QueryParam{
|
||||||
|
|
|
||||||
|
|
@ -1520,6 +1520,16 @@ func TestGetTextAndSaveText(t *testing.T) {
|
||||||
if text != "" {
|
if text != "" {
|
||||||
t.Errorf("Expected empty text, got: %s", text)
|
t.Errorf("Expected empty text, got: %s", text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also test full content
|
||||||
|
fullText, err := manager.GetText(context.Background(), file.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get full text: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullText != "" {
|
||||||
|
t.Errorf("Expected empty full text, got: %s", fullText)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test 2: SaveText and verify
|
// Test 2: SaveText and verify
|
||||||
|
|
@ -1563,7 +1573,7 @@ func TestGetTextAndSaveText(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test 4: Save long text content (simulating large document parsing)
|
// Test 4: Save long text content and verify preview vs full content
|
||||||
t.Run("SaveLongText", func(t *testing.T) {
|
t.Run("SaveLongText", func(t *testing.T) {
|
||||||
// Generate a large text content (10KB)
|
// Generate a large text content (10KB)
|
||||||
longText := strings.Repeat("This is a long text content that simulates parsing from a large document like PDF or Word. ", 100)
|
longText := strings.Repeat("This is a long text content that simulates parsing from a large document like PDF or Word. ", 100)
|
||||||
|
|
@ -1573,19 +1583,84 @@ func TestGetTextAndSaveText(t *testing.T) {
|
||||||
t.Fatalf("Failed to save long text: %v", err)
|
t.Fatalf("Failed to save long text: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
retrievedText, err := manager.GetText(context.Background(), file.ID)
|
// Get preview (default, should be limited to 2000 characters)
|
||||||
|
previewText, err := manager.GetText(context.Background(), file.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to get long text: %v", err)
|
t.Fatalf("Failed to get preview text: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if retrievedText != longText {
|
// Preview should be exactly 2000 characters (runes)
|
||||||
t.Errorf("Long text mismatch. Expected length: %d, Got: %d", len(longText), len(retrievedText))
|
previewRunes := []rune(previewText)
|
||||||
|
if len(previewRunes) != 2000 {
|
||||||
|
t.Errorf("Preview length mismatch. Expected: 2000 runes, Got: %d runes", len(previewRunes))
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("Successfully saved and retrieved long text content (%d characters)", len(retrievedText))
|
// Get full content
|
||||||
|
fullText, err := manager.GetText(context.Background(), file.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get full text: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullText != longText {
|
||||||
|
t.Errorf("Full text mismatch. Expected length: %d, Got: %d", len(longText), len(fullText))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Successfully saved long text - Preview: %d chars, Full: %d chars", len(previewText), len(fullText))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test 5: GetText with non-existent file ID
|
// Test 5: Test UTF-8 character handling in preview
|
||||||
|
t.Run("UTF8PreviewHandling", func(t *testing.T) {
|
||||||
|
// Create text with multi-byte UTF-8 characters (Chinese, emoji, etc.)
|
||||||
|
// Each Chinese character is 3 bytes, emoji is 4 bytes
|
||||||
|
chineseText := strings.Repeat("这是一个测试文本,包含中文字符。", 150) // Should exceed 2000 chars
|
||||||
|
emojiText := strings.Repeat("Hello 👋 World 🌍 ", 150)
|
||||||
|
|
||||||
|
// Test with Chinese text
|
||||||
|
err := manager.SaveText(context.Background(), file.ID, chineseText)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save Chinese text: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
previewChinese, err := manager.GetText(context.Background(), file.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get Chinese preview: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be exactly 2000 runes (characters), not bytes
|
||||||
|
if len([]rune(previewChinese)) != 2000 {
|
||||||
|
t.Errorf("Chinese preview should be 2000 runes, got: %d", len([]rune(previewChinese)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full text should be complete
|
||||||
|
fullChinese, err := manager.GetText(context.Background(), file.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get full Chinese text: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullChinese != chineseText {
|
||||||
|
t.Errorf("Chinese text mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with emoji text
|
||||||
|
err = manager.SaveText(context.Background(), file.ID, emojiText)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save emoji text: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
previewEmoji, err := manager.GetText(context.Background(), file.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get emoji preview: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len([]rune(previewEmoji)) != 2000 {
|
||||||
|
t.Errorf("Emoji preview should be 2000 runes, got: %d", len([]rune(previewEmoji)))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("UTF-8 handling verified - Chinese: %d bytes, Emoji: %d bytes",
|
||||||
|
len(previewChinese), len(previewEmoji))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 6: GetText with non-existent file ID
|
||||||
t.Run("GetTextNonExistent", func(t *testing.T) {
|
t.Run("GetTextNonExistent", func(t *testing.T) {
|
||||||
_, err := manager.GetText(context.Background(), "non-existent-id")
|
_, err := manager.GetText(context.Background(), "non-existent-id")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -1597,7 +1672,7 @@ func TestGetTextAndSaveText(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test 6: SaveText with non-existent file ID
|
// Test 7: SaveText with non-existent file ID
|
||||||
t.Run("SaveTextNonExistent", func(t *testing.T) {
|
t.Run("SaveTextNonExistent", func(t *testing.T) {
|
||||||
err := manager.SaveText(context.Background(), "non-existent-id", "some text")
|
err := manager.SaveText(context.Background(), "non-existent-id", "some text")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -1609,7 +1684,7 @@ func TestGetTextAndSaveText(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test 7: Save empty text (clear content)
|
// Test 8: Save empty text (clear content)
|
||||||
t.Run("SaveEmptyText", func(t *testing.T) {
|
t.Run("SaveEmptyText", func(t *testing.T) {
|
||||||
err := manager.SaveText(context.Background(), file.ID, "")
|
err := manager.SaveText(context.Background(), file.ID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1626,6 +1701,103 @@ func TestGetTextAndSaveText(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Test 9: Verify List doesn't include content fields by default
|
||||||
|
t.Run("ListExcludesContentByDefault", func(t *testing.T) {
|
||||||
|
// Save some text content
|
||||||
|
testText := "This text should not appear in list results by default"
|
||||||
|
err := manager.SaveText(context.Background(), file.ID, testText)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save text: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List files without specifying select fields
|
||||||
|
result, err := manager.List(context.Background(), ListOption{
|
||||||
|
Filters: map[string]interface{}{
|
||||||
|
"file_id": file.ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to list files: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Files) == 0 {
|
||||||
|
t.Fatal("Expected to find at least one file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The List method returns File structs, but we need to verify
|
||||||
|
// the database query doesn't fetch the content field
|
||||||
|
// We can verify this by checking the database directly
|
||||||
|
m := model.Select("__yao.attachment")
|
||||||
|
records, err := m.Get(model.QueryParam{
|
||||||
|
Wheres: []model.QueryWhere{
|
||||||
|
{Column: "file_id", Value: file.ID},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to query database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we do a full select, content should be present
|
||||||
|
if len(records) > 0 {
|
||||||
|
if content, ok := records[0]["content"].(string); ok && content == testText {
|
||||||
|
t.Logf("Content field exists in full query (expected): %d characters", len(content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test 10: Verify content can be explicitly selected in List
|
||||||
|
t.Run("ListIncludesContentWhenExplicitlySelected", func(t *testing.T) {
|
||||||
|
// Save some text content
|
||||||
|
testText := "This text SHOULD appear when explicitly selected"
|
||||||
|
err := manager.SaveText(context.Background(), file.ID, testText)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to save text: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List files WITH content field explicitly selected
|
||||||
|
result, err := manager.List(context.Background(), ListOption{
|
||||||
|
Select: []string{"file_id", "name", "content"},
|
||||||
|
Filters: map[string]interface{}{
|
||||||
|
"file_id": file.ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to list files with content: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Files) == 0 {
|
||||||
|
t.Fatal("Expected to find at least one file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query database directly to verify content is included
|
||||||
|
m := model.Select("__yao.attachment")
|
||||||
|
records, err := m.Get(model.QueryParam{
|
||||||
|
Select: []interface{}{"file_id", "name", "content"},
|
||||||
|
Wheres: []model.QueryWhere{
|
||||||
|
{Column: "file_id", Value: file.ID},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to query database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(records) == 0 {
|
||||||
|
t.Fatal("Expected to find record")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify content is present
|
||||||
|
if content, ok := records[0]["content"].(string); ok {
|
||||||
|
if content != testText {
|
||||||
|
t.Errorf("Expected content '%s', got '%s'", testText, content)
|
||||||
|
}
|
||||||
|
t.Logf("Content field correctly included when explicitly selected: %d characters", len(content))
|
||||||
|
} else {
|
||||||
|
t.Error("Content field not found when explicitly selected")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
err = manager.Delete(context.Background(), file.ID)
|
err = manager.Delete(context.Background(), file.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,11 @@ type FileManager interface {
|
||||||
LocalPath(ctx context.Context, fileID string) (string, string, error)
|
LocalPath(ctx context.Context, fileID string) (string, string, error)
|
||||||
|
|
||||||
// GetText retrieves the parsed text content for a file
|
// GetText retrieves the parsed text content for a file
|
||||||
GetText(ctx context.Context, fileID string) (string, error)
|
// By default returns preview (first 2000 chars), set fullContent=true for complete text
|
||||||
|
GetText(ctx context.Context, fileID string, fullContent ...bool) (string, error)
|
||||||
|
|
||||||
// SaveText saves the parsed text content for a file
|
// SaveText saves the parsed text content for a file
|
||||||
|
// Automatically saves both full content and preview
|
||||||
SaveText(ctx context.Context, fileID string, text string) error
|
SaveText(ctx context.Context, fileID string, text string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
284
data/bindata.go
284
data/bindata.go
File diff suppressed because it is too large
Load diff
|
|
@ -49,7 +49,14 @@
|
||||||
"name": "content",
|
"name": "content",
|
||||||
"type": "longText",
|
"type": "longText",
|
||||||
"label": "Content",
|
"label": "Content",
|
||||||
"comment": "Parsed text content from image, pdf, word and other file types",
|
"comment": "Full parsed text content from image, pdf, word and other file types",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "content_preview",
|
||||||
|
"type": "text",
|
||||||
|
"label": "Content Preview",
|
||||||
|
"comment": "Preview of parsed text content (first 2000 characters)",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue