mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-01-22 22:34:20 -06:00
Compare commits
3 Commits
feature-au
...
feature-bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0df0f3a21f | ||
|
|
d81488d054 | ||
|
|
8dc4f34743 |
1
.github/release-drafter.yml
vendored
1
.github/release-drafter.yml
vendored
@@ -44,7 +44,6 @@ include-labels:
|
|||||||
- 'notable'
|
- 'notable'
|
||||||
exclude-labels:
|
exclude-labels:
|
||||||
- 'skip-changelog'
|
- 'skip-changelog'
|
||||||
filter-by-commitish: true
|
|
||||||
category-template: '### $TITLE'
|
category-template: '### $TITLE'
|
||||||
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
|
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
|
||||||
change-title-escapes: '\<*_&#@'
|
change-title-escapes: '\<*_&#@'
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ repos:
|
|||||||
- json
|
- json
|
||||||
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
||||||
- repo: https://github.com/rbubley/mirrors-prettier
|
- repo: https://github.com/rbubley/mirrors-prettier
|
||||||
rev: 'v3.6.2'
|
rev: 'v3.8.0'
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types_or:
|
types_or:
|
||||||
@@ -49,7 +49,7 @@ repos:
|
|||||||
- 'prettier-plugin-organize-imports@4.1.0'
|
- 'prettier-plugin-organize-imports@4.1.0'
|
||||||
# Python hooks
|
# Python hooks
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.14.5
|
rev: v0.14.13
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
@@ -76,7 +76,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: shellcheck
|
- id: shellcheck
|
||||||
- repo: https://github.com/google/yamlfmt
|
- repo: https://github.com/google/yamlfmt
|
||||||
rev: v0.20.0
|
rev: v0.21.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: yamlfmt
|
- id: yamlfmt
|
||||||
exclude: "^src-ui/pnpm-lock.yaml"
|
exclude: "^src-ui/pnpm-lock.yaml"
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## paperless-ngx 2.20.5
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- Fix: ensure horizontal scroll for long tag names in list, wrap tags without parent [@shamoon](https://github.com/shamoon) ([#11811](https://github.com/paperless-ngx/paperless-ngx/pull/11811))
|
|
||||||
- Fix: use explicit order field for workflow actions [@shamoon](https://github.com/shamoon) [@stumpylog](https://github.com/stumpylog) ([#11781](https://github.com/paperless-ngx/paperless-ngx/pull/11781))
|
|
||||||
|
|
||||||
### All App Changes
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>2 changes</summary>
|
|
||||||
|
|
||||||
- Fix: ensure horizontal scroll for long tag names in list, wrap tags without parent [@shamoon](https://github.com/shamoon) ([#11811](https://github.com/paperless-ngx/paperless-ngx/pull/11811))
|
|
||||||
- Fix: use explicit order field for workflow actions [@shamoon](https://github.com/shamoon) [@stumpylog](https://github.com/stumpylog) ([#11781](https://github.com/paperless-ngx/paperless-ngx/pull/11781))
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## paperless-ngx 2.20.4
|
## paperless-ngx 2.20.4
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "paperless-ngx"
|
name = "paperless-ngx"
|
||||||
version = "2.20.5"
|
version = "2.20.4"
|
||||||
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
@@ -19,15 +19,15 @@ dependencies = [
|
|||||||
"azure-ai-documentintelligence>=1.0.2",
|
"azure-ai-documentintelligence>=1.0.2",
|
||||||
"babel>=2.17",
|
"babel>=2.17",
|
||||||
"bleach~=6.3.0",
|
"bleach~=6.3.0",
|
||||||
"celery[redis]~=5.5.1",
|
"celery[redis]~=5.6.2",
|
||||||
"channels~=4.2",
|
"channels~=4.2",
|
||||||
"channels-redis~=4.2",
|
"channels-redis~=4.2",
|
||||||
"concurrent-log-handler~=0.9.25",
|
"concurrent-log-handler~=0.9.25",
|
||||||
"dateparser~=1.2",
|
"dateparser~=1.2",
|
||||||
# WARNING: django does not use semver.
|
# WARNING: django does not use semver.
|
||||||
# Only patch versions are guaranteed to not introduce breaking changes.
|
# Only patch versions are guaranteed to not introduce breaking changes.
|
||||||
"django~=5.2.5",
|
"django==5.2.10",
|
||||||
"django-allauth[mfa,socialaccount]~=65.12.1",
|
"django-allauth[mfa,socialaccount]~=65.13.0",
|
||||||
"django-auditlog~=3.4.1",
|
"django-auditlog~=3.4.1",
|
||||||
"django-cachalot~=2.8.0",
|
"django-cachalot~=2.8.0",
|
||||||
"django-celery-results~=2.6.0",
|
"django-celery-results~=2.6.0",
|
||||||
@@ -80,7 +80,7 @@ dependencies = [
|
|||||||
"torch~=2.9.1",
|
"torch~=2.9.1",
|
||||||
"tqdm~=4.67.1",
|
"tqdm~=4.67.1",
|
||||||
"watchdog~=6.0",
|
"watchdog~=6.0",
|
||||||
"whitenoise~=6.9",
|
"whitenoise~=6.11",
|
||||||
"whoosh-reloaded>=2.7.5",
|
"whoosh-reloaded>=2.7.5",
|
||||||
"zxing-cpp~=2.3.0",
|
"zxing-cpp~=2.3.0",
|
||||||
]
|
]
|
||||||
@@ -89,13 +89,13 @@ optional-dependencies.mariadb = [
|
|||||||
"mysqlclient~=2.2.7",
|
"mysqlclient~=2.2.7",
|
||||||
]
|
]
|
||||||
optional-dependencies.postgres = [
|
optional-dependencies.postgres = [
|
||||||
"psycopg[c,pool]==3.2.12",
|
"psycopg[c,pool]==3.3",
|
||||||
# Direct dependency for proper resolution of the pre-built wheels
|
# Direct dependency for proper resolution of the pre-built wheels
|
||||||
"psycopg-c==3.2.12",
|
"psycopg-c==3.3",
|
||||||
"psycopg-pool==3.3",
|
"psycopg-pool==3.3",
|
||||||
]
|
]
|
||||||
optional-dependencies.webserver = [
|
optional-dependencies.webserver = [
|
||||||
"granian[uvloop]~=2.5.1",
|
"granian[uvloop]~=2.6.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
@@ -152,7 +152,7 @@ typing = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
required-version = ">=0.5.14"
|
required-version = ">=0.9.0"
|
||||||
package = false
|
package = false
|
||||||
environments = [
|
environments = [
|
||||||
"sys_platform == 'darwin'",
|
"sys_platform == 'darwin'",
|
||||||
@@ -162,8 +162,8 @@ environments = [
|
|||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
# Markers are chosen to select these almost exclusively when building the Docker image
|
# Markers are chosen to select these almost exclusively when building the Docker image
|
||||||
psycopg-c = [
|
psycopg-c = [
|
||||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-bookworm-3.2.12/psycopg_c-3.2.12-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.0/psycopg_c-3.3.0-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
||||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-bookworm-3.2.12/psycopg_c-3.2.12-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
{ url = "https://github.com/paperless-ngx/builder/releases/download/psycopg-trixie-3.3.0/psycopg_c-3.3.0-cp312-cp312-linux_aarch64.whl", marker = "sys_platform == 'linux' and platform_machine == 'aarch64' and python_version == '3.12'" },
|
||||||
]
|
]
|
||||||
zxing-cpp = [
|
zxing-cpp = [
|
||||||
{ url = "https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
{ url = "https://github.com/paperless-ngx/builder/releases/download/zxing-2.3.0/zxing_cpp-2.3.0-cp312-cp312-linux_x86_64.whl", marker = "sys_platform == 'linux' and platform_machine == 'x86_64' and python_version == '3.12'" },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paperless-ngx-ui",
|
"name": "paperless-ngx-ui",
|
||||||
"version": "2.20.5",
|
"version": "2.20.4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
|
<ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
|
||||||
<div class="tag-option-row d-flex align-items-center" [class.w-auto]="!getTag(item.id)?.parent">
|
<div class="tag-option-row d-flex align-items-center">
|
||||||
@if (item.id && tags) {
|
@if (item.id && tags) {
|
||||||
@if (getTag(item.id)?.parent) {
|
@if (getTag(item.id)?.parent) {
|
||||||
<i-bs name="list-nested" class="me-1"></i-bs>
|
<i-bs name="list-nested" class="me-1"></i-bs>
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dropdown hierarchy reveal for ng-select options
|
// Dropdown hierarchy reveal for ng-select options
|
||||||
:host ::ng-deep .ng-dropdown-panel .ng-option {
|
::ng-deep .ng-dropdown-panel .ng-option {
|
||||||
overflow-x: auto !important;
|
overflow-x: scroll;
|
||||||
|
|
||||||
.tag-option-row {
|
.tag-option-row {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -41,12 +41,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:host ::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-reveal,
|
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-reveal,
|
||||||
:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-reveal {
|
::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-reveal {
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-indicator,
|
::ng-deep .ng-dropdown-panel .ng-option:hover .hierarchy-indicator,
|
||||||
:host ::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-indicator {
|
::ng-deep .ng-dropdown-panel .ng-option.ng-option-marked .hierarchy-indicator {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,10 +285,10 @@ export class DocumentDetailComponent
|
|||||||
if (
|
if (
|
||||||
element &&
|
element &&
|
||||||
element.nativeElement.offsetParent !== null &&
|
element.nativeElement.offsetParent !== null &&
|
||||||
this.nav?.activeId == DocumentDetailNavIDs.Preview
|
this.nav?.activeId == 4
|
||||||
) {
|
) {
|
||||||
// its visible
|
// its visible
|
||||||
setTimeout(() => this.nav?.select(DocumentDetailNavIDs.Details))
|
setTimeout(() => this.nav?.select(1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const environment = {
|
|||||||
apiVersion: '9', // match src/paperless/settings.py
|
apiVersion: '9', // match src/paperless/settings.py
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
tag: 'prod',
|
tag: 'prod',
|
||||||
version: '2.20.5',
|
version: '2.20.4',
|
||||||
webSocketHost: window.location.host,
|
webSocketHost: window.location.host,
|
||||||
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
||||||
webSocketBaseUrl: base_url.pathname + 'ws/',
|
webSocketBaseUrl: base_url.pathname + 'ws/',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from django.db import models
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("documents", "1075_workflowaction_order"),
|
("documents", "1074_workflowrun_deleted_at_workflowrun_restored_at_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@@ -12,7 +12,7 @@ def populate_action_order(apps, schema_editor):
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("documents", "1074_workflowrun_deleted_at_workflowrun_restored_at_and_more"),
|
("documents", "1075_alter_paperlesstask_task_name"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
__version__: Final[tuple[int, int, int]] = (2, 20, 5)
|
__version__: Final[tuple[int, int, int]] = (2, 20, 4)
|
||||||
# Version string like X.Y.Z
|
# Version string like X.Y.Z
|
||||||
__full_version_str__: Final[str] = ".".join(map(str, __version__))
|
__full_version_str__: Final[str] = ".".join(map(str, __version__))
|
||||||
# Version string like X.Y
|
# Version string like X.Y
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import timedelta
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import faiss
|
import faiss
|
||||||
import llama_index.core.settings as llama_settings
|
import llama_index.core.settings as llama_settings
|
||||||
import tqdm
|
import tqdm
|
||||||
from celery import states
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
|
||||||
from llama_index.core import Document as LlamaDocument
|
from llama_index.core import Document as LlamaDocument
|
||||||
from llama_index.core import StorageContext
|
from llama_index.core import StorageContext
|
||||||
from llama_index.core import VectorStoreIndex
|
from llama_index.core import VectorStoreIndex
|
||||||
@@ -24,7 +21,6 @@ from llama_index.core.text_splitter import TokenTextSplitter
|
|||||||
from llama_index.vector_stores.faiss import FaissVectorStore
|
from llama_index.vector_stores.faiss import FaissVectorStore
|
||||||
|
|
||||||
from documents.models import Document
|
from documents.models import Document
|
||||||
from documents.models import PaperlessTask
|
|
||||||
from paperless_ai.embedding import build_llm_index_text
|
from paperless_ai.embedding import build_llm_index_text
|
||||||
from paperless_ai.embedding import get_embedding_dim
|
from paperless_ai.embedding import get_embedding_dim
|
||||||
from paperless_ai.embedding import get_embedding_model
|
from paperless_ai.embedding import get_embedding_model
|
||||||
@@ -32,29 +28,6 @@ from paperless_ai.embedding import get_embedding_model
|
|||||||
logger = logging.getLogger("paperless_ai.indexing")
|
logger = logging.getLogger("paperless_ai.indexing")
|
||||||
|
|
||||||
|
|
||||||
def queue_llm_index_update_if_needed(*, rebuild: bool, reason: str) -> bool:
|
|
||||||
from documents.tasks import llmindex_index
|
|
||||||
|
|
||||||
has_running = PaperlessTask.objects.filter(
|
|
||||||
task_name=PaperlessTask.TaskName.LLMINDEX_UPDATE,
|
|
||||||
status__in=[states.PENDING, states.STARTED],
|
|
||||||
).exists()
|
|
||||||
has_recent = PaperlessTask.objects.filter(
|
|
||||||
task_name=PaperlessTask.TaskName.LLMINDEX_UPDATE,
|
|
||||||
date_created__gte=(timezone.now() - timedelta(minutes=5)),
|
|
||||||
).exists()
|
|
||||||
if has_running or has_recent:
|
|
||||||
return False
|
|
||||||
|
|
||||||
llmindex_index.delay(rebuild=rebuild, scheduled=False, auto=True)
|
|
||||||
logger.warning(
|
|
||||||
"Queued LLM index update%s: %s",
|
|
||||||
" (rebuild)" if rebuild else "",
|
|
||||||
reason,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_or_create_storage_context(*, rebuild=False):
|
def get_or_create_storage_context(*, rebuild=False):
|
||||||
"""
|
"""
|
||||||
Loads or creates the StorageContext (vector store, docstore, index store).
|
Loads or creates the StorageContext (vector store, docstore, index store).
|
||||||
@@ -120,10 +93,6 @@ def load_or_build_index(nodes=None):
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning("Failed to load index from storage: %s", e)
|
logger.warning("Failed to load index from storage: %s", e)
|
||||||
if not nodes:
|
if not nodes:
|
||||||
queue_llm_index_update_if_needed(
|
|
||||||
rebuild=vector_store_file_exists(),
|
|
||||||
reason="LLM index missing or invalid while loading.",
|
|
||||||
)
|
|
||||||
logger.info("No nodes provided for index creation.")
|
logger.info("No nodes provided for index creation.")
|
||||||
raise
|
raise
|
||||||
return VectorStoreIndex(
|
return VectorStoreIndex(
|
||||||
@@ -281,21 +250,7 @@ def query_similar_documents(
|
|||||||
"""
|
"""
|
||||||
Runs a similarity query and returns top-k similar Document objects.
|
Runs a similarity query and returns top-k similar Document objects.
|
||||||
"""
|
"""
|
||||||
if not vector_store_file_exists():
|
index = load_or_build_index()
|
||||||
queue_llm_index_update_if_needed(
|
|
||||||
rebuild=False,
|
|
||||||
reason="LLM index not found for similarity query.",
|
|
||||||
)
|
|
||||||
return []
|
|
||||||
|
|
||||||
try:
|
|
||||||
index = load_or_build_index()
|
|
||||||
except ValueError:
|
|
||||||
queue_llm_index_update_if_needed(
|
|
||||||
rebuild=True,
|
|
||||||
reason="LLM index failed to load for similarity query.",
|
|
||||||
)
|
|
||||||
return []
|
|
||||||
|
|
||||||
# constrain only the node(s) that match the document IDs, if given
|
# constrain only the node(s) that match the document IDs, if given
|
||||||
doc_node_ids = (
|
doc_node_ids = (
|
||||||
|
|||||||
@@ -299,15 +299,11 @@ def test_query_similar_documents(
|
|||||||
with (
|
with (
|
||||||
patch("paperless_ai.indexing.get_or_create_storage_context") as mock_storage,
|
patch("paperless_ai.indexing.get_or_create_storage_context") as mock_storage,
|
||||||
patch("paperless_ai.indexing.load_or_build_index") as mock_load_or_build_index,
|
patch("paperless_ai.indexing.load_or_build_index") as mock_load_or_build_index,
|
||||||
patch(
|
|
||||||
"paperless_ai.indexing.vector_store_file_exists",
|
|
||||||
) as mock_vector_store_exists,
|
|
||||||
patch("paperless_ai.indexing.VectorIndexRetriever") as mock_retriever_cls,
|
patch("paperless_ai.indexing.VectorIndexRetriever") as mock_retriever_cls,
|
||||||
patch("paperless_ai.indexing.Document.objects.filter") as mock_filter,
|
patch("paperless_ai.indexing.Document.objects.filter") as mock_filter,
|
||||||
):
|
):
|
||||||
mock_storage.return_value = MagicMock()
|
mock_storage.return_value = MagicMock()
|
||||||
mock_storage.return_value.persist_dir = temp_llm_index_dir
|
mock_storage.return_value.persist_dir = temp_llm_index_dir
|
||||||
mock_vector_store_exists.return_value = True
|
|
||||||
|
|
||||||
mock_index = MagicMock()
|
mock_index = MagicMock()
|
||||||
mock_load_or_build_index.return_value = mock_index
|
mock_load_or_build_index.return_value = mock_index
|
||||||
|
|||||||
Reference in New Issue
Block a user