Skip to content

Commit 3dfc8cb

Browse files
authored
Merge pull request #1102 from Open-Source-Legal/claude/explore-docxodus-G49qt
Add first-class DOCX support with Docxodus microservice and WASM renderer
2 parents d9b5632 + 1209880 commit 3dfc8cb

49 files changed

Lines changed: 3678 additions & 126 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.envs/.test/.django

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,5 @@ VECTOR_EMBEDDER_API_KEY=abc123
5858
MULTIMODAL_EMBEDDER_URL=http://multimodal-embedder:8000
5959
MULTIMODAL_EMBEDDER_API_KEY=abc123
6060
DOCLING_PARSER_SERVICE_URL=http://docling-parser:8000/parse/
61+
DOCXODUS_PARSER_SERVICE_URL=http://docxodus-parser:8080/parse
62+
DOCXODUS_PARSER_TIMEOUT=120

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6464

6565
### Added
6666

67+
- **First-class DOCX document support via Docxodus pipeline**: Added a complete parallel ingestion pipeline and rendering tree for DOCX files, bringing Word document support alongside existing PDF and TXT pipelines. Changes include:
68+
- **Backend: Docxodus microservice** (`docxodus-service/`): .NET 8 minimal API wrapping `OpenContractExporter.Export()` to produce OpenContractDocExport-compatible JSON with structural annotations and character offsets from DOCX files. Multi-stage Docker build exposed on port 8080.
69+
- **Backend: DocxodusServiceParser** (`opencontractserver/pipeline/parsers/docxodus_parser.py`): REST parser that sends base64-encoded DOCX to the microservice, normalizes camelCase→snake_case response fields, and handles transient/permanent error classification.
70+
- **Backend: DocxThumbnailGenerator** (`opencontractserver/pipeline/thumbnailers/docx_thumbnailer.py`): Two-tier thumbnail approach — extracts embedded thumbnails from DOCX ZIP archives (`docProps/thumbnail.jpeg`), falling back to text-based thumbnails via XML parsing of `word/document.xml`.
71+
- **Frontend: DocxAnnotator** (`frontend/src/components/annotator/renderers/docx/DocxAnnotator.tsx`): WASM-powered DOCX renderer using `docxodus` npm package's `convertDocxToHtmlWithExternalAnnotations()` for annotation projection onto native DOCX HTML output. Supports text selection for new annotation creation via `findTextOccurrences()`.
72+
- **Frontend: DocxAnnotatorWrapper** (`frontend/src/components/annotator/components/wrappers/DocxAnnotatorWrapper.tsx`): State management wrapper mirroring TxtAnnotatorWrapper pattern — manages annotation CRUD, chat sources, text search, and ref registration.
73+
- **Frontend: DocumentKnowledgeBase integration**: DOCX loading flow (fetches raw bytes + extracted text) and renderer dispatch added to both query handlers.
74+
- **Frontend utilities**: `isDocxFileType()` in `frontend/src/utils/files.ts`, `DOCX_MIME_TYPE` constant, `docxBytesAtom` / `useDocxBytes()` hook in DocumentAtom, `getDocxBytes()` in cachedRest.
75+
- **Docker Compose**: `docxodus-parser` service added to `local.yml`, `production.yml`, and `test.yml` with dependency wiring.
76+
- **Dependencies**: `docxodus@5.5.0` and `dompurify@3.3.3` added to frontend.
77+
- **Backend tests**: `test_doc_parser_docxodus.py` with parser unit tests (success, timeout, connection error, normalization) and thumbnailer tests (text preview, embedded thumbnail, invalid DOCX handling).
78+
- **Frontend tests**: `DocxAnnotator.ct.tsx` component test with `docScreenshot` captures.
79+
6780
- **Richer social media link previews for corpus and document links**: Improved the Cloudflare OG worker to generate better social tags. Changes include:
6881
- Corpus descriptions are now included in OG/Twitter description tags, combined with document count (e.g. "Corpus description — 15 documents")
6982
- Document-in-corpus links now surface the parent corpus description when the document lacks its own description

config/graphql/annotation_types.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,20 @@ class AnnotationInputType(AnnotatePermissionsForReadMixin, graphene.InputObjectT
5050

5151
class AnnotationType(AnnotatePermissionsForReadMixin, DjangoObjectType):
5252
json = GenericScalar() # noqa
53+
annotation_type = graphene.String(
54+
description="Annotation type (e.g. TOKEN_LABEL, SPAN_LABEL). "
55+
"Returns raw DB value to avoid enum serialization errors on invalid data.",
56+
)
5357
feedback_count = graphene.Int(description="Count of user feedback")
5458
content_modalities = graphene.List(
5559
graphene.String,
5660
description="Content modalities present in this annotation: TEXT, IMAGE, etc.",
5761
)
5862

63+
def resolve_annotation_type(self, info):
64+
"""Return annotation_type as a plain string to tolerate invalid DB values."""
65+
return self.annotation_type or ""
66+
5967
def resolve_content_modalities(self, info):
6068
"""Return content modalities list from model."""
6169
return self.content_modalities or []
-2.23 KB
Loading
13.5 KB
Loading
13.5 KB
Loading
13.5 KB
Loading
72.9 KB
Loading

frontend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
"axios": "^1.13.5",
4242
"d3": "^7.9.0",
4343
"date-fns": "^4.1.0",
44+
"docxodus": "5.5.3",
45+
"dompurify": "^3.3.3",
4446
"framer-motion": "6.*",
4547
"fuse.js": "^6.5.3",
4648
"graphql": "^16.2.0",
@@ -137,6 +139,7 @@
137139
"@playwright/test": "^1.52.0",
138140
"@testing-library/react-hooks": "^8.0.1",
139141
"@types/d3": "^7.4.3",
142+
"@types/dompurify": "^3.2.0",
140143
"@types/json-schema": "^7.0.15",
141144
"@types/lodash.uniqueid": "^4.0.9",
142145
"@types/react-helmet-async": "^1.0.3",

frontend/playwright/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as pdfjs from "pdfjs-dist";
1515
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
1616
import { ThemeProvider } from "../src/theme/ThemeProvider";
1717
import { allStyles } from "@os-legal/ui";
18+
import { setWasmBasePath } from "docxodus";
1819

1920
// Create a type for the Jotai Store
2021
type Store = ReturnType<typeof createStore>;
@@ -44,9 +45,14 @@ beforeMount(async ({ App }: BeforeMountParams) => {
4445
console.log(`[Playwright Hook] Before mounting component with providers`);
4546

4647
// Configure PDF.js to use a worker - https://github.com/mozilla/pdf.js/issues/10478
47-
//GlobalWorkerOptions.workerSrc = '';
4848
pdfjs.GlobalWorkerOptions.workerSrc = workerSrc;
4949

50+
// Configure Docxodus WASM path for Playwright CT environment.
51+
// In normal dev/prod, import.meta.url auto-detects the path, but in
52+
// Playwright CT the test bundle compilation breaks auto-detection.
53+
// Tests use page.route() to intercept /docxodus-wasm/** and serve from disk.
54+
setWasmBasePath("/docxodus-wasm/");
55+
5056
// Create a fresh Jotai store for this test
5157
window.jotaiStore = createStore();
5258

0 commit comments

Comments
 (0)