Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
!application/single_app/**
!docker-customization/
!docker-customization/**
!LICENSE

.git/
.github/
Expand All @@ -30,4 +31,6 @@ scripts/

application/single_app/flask_session/
application/single_app/uploaded_openapi_files/
application/single_app/instance/
application/single_app/instance/
application/single_app/native/onenote_extractor/target/
application/single_app/native/onenote_extractor/bin/
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,6 @@ local_storage_state.json
.pytest_cache
.playwright-mcp
.impeccable

# Local OneNote native builds
application/single_app/native/onenote_extractor/target/
24 changes: 23 additions & 1 deletion application/single_app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@
ARG INSTALL_PLAYWRIGHT_CHROMIUM=true
ARG INSTALL_AUDIO_FFMPEG=true

FROM rust:1.85.1@sha256:e51d0265072d2d9d5d320f6a44dde6b9ef13653b035098febd68cce8fa7c0bc4 AS onenote-builder

COPY docker-customization/custom-ca-certificates/ /usr/local/share/ca-certificates/

Check warning on line 9 in application/single_app/Dockerfile

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
RUN update-ca-certificates

Check warning on line 10 in application/single_app/Dockerfile

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
WORKDIR /build
COPY application/single_app/native/onenote_extractor/ ./
COPY LICENSE /onenote-source/wrapper/LICENSE
RUN cargo build --release --locked \
&& mkdir -p /onenote-source/wrapper/.cargo \
&& cargo vendor --locked --versioned-dirs /onenote-source/dependencies > /onenote-source/vendor-config.toml \
&& sed 's|directory = "/onenote-source/dependencies"|directory = "../dependencies"|' /onenote-source/vendor-config.toml > /onenote-source/wrapper/.cargo/config.toml \
&& rm /onenote-source/vendor-config.toml \
&& cp Cargo.toml Cargo.lock /onenote-source/wrapper/ \
&& cp -R src /onenote-source/wrapper/

FROM mcr.microsoft.com/azurelinux/distroless/python:3.12 AS onenote-runtime

Check warning on line 22 in application/single_app/Dockerfile

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.

COPY --from=onenote-builder /build/target/release/simplechat-onenote-extractor /app/native/onenote_extractor/bin/simplechat-onenote-extractor
COPY --from=onenote-builder /build/THIRD_PARTY_NOTICES.txt /usr/share/simplechat/onenote-extractor/THIRD_PARTY_NOTICES.txt
COPY --from=onenote-builder /build/licenses/ /usr/share/simplechat/onenote-extractor/licenses/
COPY --from=onenote-builder /onenote-source/ /usr/share/simplechat/onenote-extractor/source/

FROM mcr.microsoft.com/azurelinux/base/python:3.12 AS builder

ARG UID
Expand Down Expand Up @@ -176,7 +198,7 @@
;; \
esac

FROM mcr.microsoft.com/azurelinux/distroless/python:3.12
FROM onenote-runtime

Check failure on line 201 in application/single_app/Dockerfile

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Critical - Docker base image is floating or uses latest. Recommendation%3A Pin Docker images to an immutable digest or reviewed exact version tag.

ARG UID
ARG GID
Expand Down
10 changes: 9 additions & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
mimetypes.add_type('font/ttf', '.ttf')
mimetypes.add_type('font/otf', '.otf')
mimetypes.add_type('application/vnd.ms-outlook', '.msg')
mimetypes.add_type('application/onenote', '.one')
mimetypes.add_type('application/onenote', '.onepkg')
mimetypes.add_type('application/xml', '.xsd')
import openpyxl
import xlrd
Expand Down Expand Up @@ -98,7 +100,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.044"
VERSION = "0.261.045"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down Expand Up @@ -188,6 +190,7 @@ def _split_env_list(raw_value, lowercase=False):
SCHEMA_EXTENSIONS = {'xsd'}
VISIO_EXTENSIONS = {'vsdx'}
EMAIL_EXTENSIONS = {'msg'}
ONENOTE_EXTENSIONS = {'one', 'onepkg'}

# Updates to image, video, audio, or Visio extensions should also be made in static/js/chat/chat-enhanced-citations.js if the new file types can be natively rendered in the browser.
IMAGE_EXTENSIONS = {'jpg', 'jpeg', 'png', 'bmp', 'tiff', 'tif', 'heif', 'heic'}
Expand Down Expand Up @@ -222,6 +225,7 @@ def get_allowed_extensions(enable_video=False, enable_audio=False):
extensions.update(SCHEMA_EXTENSIONS)
extensions.update(VISIO_EXTENSIONS)
extensions.update(EMAIL_EXTENSIONS)
extensions.update(ONENOTE_EXTENSIONS)

if enable_video:
extensions.update(VIDEO_EXTENSIONS)
Expand Down Expand Up @@ -252,6 +256,10 @@ def get_allowed_extension_categories(enable_video=False, enable_audio=False, ena
'name': 'Email and diagrams',
'extensions': EMAIL_EXTENSIONS | VISIO_EXTENSIONS,
},
{
'name': 'OneNote (typed text and tables)',
'extensions': ONENOTE_EXTENSIONS,
},
]

if enable_audio:
Expand Down
145 changes: 145 additions & 0 deletions application/single_app/functions_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
hold_data_management_search_write_slot,
)
from functions_visio import build_visio_page_markdown, parse_vsdx_pages
from functions_onenote import ONENOTE_MAX_CHUNKS, OneNoteExtractionError, extract_onenote
from functions_content import *
from functions_office_media import extract_office_embedded_images_with_diagnostics
from functions_content_understanding import analyze_image_with_content_understanding
Expand Down Expand Up @@ -8807,6 +8808,143 @@

return total_chunks_saved, total_embedding_tokens, embedding_model_name

def process_onenote(document_id, user_id, temp_file_path, original_filename, enable_enhanced_citations, update_callback, group_id=None, public_workspace_id=None, auto_extract_metadata=True):

Check warning on line 8811 in application/single_app/functions_documents.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
"""Index current OneNote typed text without losing page and section context."""
scope = {}
if public_workspace_id is not None:

Check warning on line 8814 in application/single_app/functions_documents.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
scope["public_workspace_id"] = public_workspace_id

Check warning on line 8815 in application/single_app/functions_documents.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
elif group_id is not None:
scope["group_id"] = group_id

try:

Check warning on line 8819 in application/single_app/functions_documents.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
settings = get_settings()

Check warning on line 8820 in application/single_app/functions_documents.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
max_file_size_bytes = int(settings.get("max_file_size_mb", 16) * 1024 * 1024)
chunk_config = get_chunk_size_config(settings)
target_words = chunk_config["txt"]["value"]
max_characters = get_embedding_safe_chunk_characters(settings)

Check warning on line 8824 in application/single_app/functions_documents.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
update_callback(status="Extracting OneNote typed text and tables...")
extraction = extract_onenote(
temp_file_path, max_file_size_bytes, original_filename=original_filename
)

chunks = []
ancestors = []
previous_section = None
for source_page_number, page in enumerate(extraction.pages, start=1):
if previous_section != page.section_path:
ancestors = []
previous_section = page.section_path
while ancestors and ancestors[-1][0] >= page.level:
ancestors.pop()
page_title = " ".join(page.title.split()) or "(untitled page)"
page_titles = [title for _, title in ancestors] + [page_title]
ancestors.append((page.level, page_title))
if not page.text.strip():
continue

section_label = " / ".join(" ".join(part.split()) for part in page.section_path)
page_label = " / ".join(page_titles)
header = (
f"Section: {section_label}\n"
f"Page: {page_label}\n"
f"Source page: {source_page_number}\n\n"
)
available_words = target_words - count_words(header)
available_characters = max_characters - len(header)
if available_words < 1 or available_characters < 1:
raise OneNoteExtractionError("limit_exceeded")
pieces = split_text_by_word_limit(page.text, available_words)
pieces = split_oversized_chunks(pieces, available_characters)
for piece in pieces:
if not piece.strip():
continue
content = f"{header}{piece}"
if (
len(chunks) >= ONENOTE_MAX_CHUNKS
or len(content) > max_characters
or count_words(content) > target_words
):
raise OneNoteExtractionError("limit_exceeded")
chunks.append({
"page_text_content": content,
"page_number": len(chunks) + 1,
"file_name": original_filename,
})
if not chunks:
raise OneNoteExtractionError("no_text")

# Validate the complete notebook and every chunk before any searchable writes.

Check warning on line 8876 in application/single_app/functions_documents.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
update_callback(
number_of_pages=len(chunks),
onenote_source_page_count=len(extraction.pages),
onenote_section_count=extraction.sections,
onenote_excluded_content=extraction.excluded_content,
status="Indexing OneNote typed text; images, handwriting, and attachments excluded",
)
if enable_enhanced_citations:
upload_to_blob(
temp_file_path=temp_file_path,
user_id=user_id,
document_id=document_id,
blob_filename=original_filename,
update_callback=update_callback,
**scope,
)

total_chunks_saved = 0
total_embedding_tokens = 0
embedding_model_name = None
for start in range(0, len(chunks), 32):
batch = chunks[start:start + 32]
update_callback(
current_file_chunk=start + 1,
status=f"Indexing OneNote chunks {start + 1}-{start + len(batch)}/{len(chunks)}...",
)
token_usage = save_chunks_batch(batch, user_id, document_id, **scope)
total_chunks_saved += len(batch)
if token_usage:
total_embedding_tokens += token_usage.get("total_tokens", 0)
if not embedding_model_name:
embedding_model_name = token_usage.get("model_deployment_name")
update_callback(num_chunks=total_chunks_saved, current_file_chunk=total_chunks_saved)

if auto_extract_metadata:
_run_final_metadata_extraction(
document_id,
user_id,
total_chunks_saved,
settings.get("enable_extract_meta_data", False),
update_callback,
**scope,
)
log_event(
"[ONENOTE_INGESTION] Indexed OneNote typed content.",
extra={
"document_id": document_id,
"sections": extraction.sections,
"source_pages": len(extraction.pages),
"chunks": total_chunks_saved,
"excluded_content": extraction.excluded_content,
},
debug_only=True,
)
return total_chunks_saved, total_embedding_tokens, embedding_model_name
except OneNoteExtractionError as exc:
log_event(
"[ONENOTE_INGESTION] OneNote processing could not be completed.",
extra={"document_id": document_id, "error_code": exc.code},
level=logging.WARNING,
)
raise
except Exception as exc:
log_event(
"[ONENOTE_INGESTION] OneNote indexing failed.",
extra={"document_id": document_id, "error_type": type(exc).__name__},
level=logging.ERROR,
)
raise OneNoteExtractionError("indexing_failed") from exc


def process_visio(document_id, user_id, temp_file_path, original_filename, enable_enhanced_citations, update_callback, group_id=None, public_workspace_id=None, auto_extract_metadata=True):
"""Processes Visio VSDX files as one searchable chunk per page."""
is_group = group_id is not None
Expand Down Expand Up @@ -10652,6 +10790,7 @@
audio_extensions = tuple('.' + ext for ext in AUDIO_EXTENSIONS)
visio_extensions = tuple('.' + ext for ext in VISIO_EXTENSIONS)
email_extensions = tuple('.' + ext for ext in EMAIL_EXTENSIONS)
onenote_extensions = tuple('.' + ext for ext in ONENOTE_EXTENSIONS)

# --- Define update_document callback wrapper ---
# This makes it easier to pass the update function to helpers without repeating args
Expand Down Expand Up @@ -10793,6 +10932,10 @@
total_chunks_saved, total_embedding_tokens, embedding_model_name = result
else:
total_chunks_saved = result
elif file_ext in onenote_extensions:
total_chunks_saved, total_embedding_tokens, embedding_model_name = process_onenote(
**{k: v for k, v in processor_args_without_auto_metadata.items() if k != "file_ext"}
)
elif file_ext in email_extensions:
result = process_msg(**{k: v for k, v in processor_args_without_auto_metadata.items() if k != "file_ext"})
if isinstance(result, tuple) and len(result) == 3:
Expand Down Expand Up @@ -10856,6 +10999,8 @@
tabular_extensions,
metadata_extraction_result
)
if file_ext in onenote_extensions:
final_status += " - OneNote typed text only; images, handwriting, and attachments excluded"

# Final update uses the total chunks saved across all steps/sheets
# For DI types, number_of_pages might have been updated during DI processing,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def log_event(*args, **kwargs):
NARRATIVE_SOURCE_EXTENSIONS = frozenset({
".txt", ".doc", ".docm", ".docx", ".html", ".htm", ".md", ".markdown",
".json", ".xml", ".yaml", ".yml", ".log", ".pdf", ".ppt", ".pptx",
".msg", ".vsdx", ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif",
".msg", ".vsdx", ".one", ".onepkg", ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif",
".heif", ".heic", ".3ga", ".aac", ".ac3", ".aif", ".aifc", ".aiff",
".amr", ".ape", ".au", ".caf", ".dts", ".f4a", ".flac", ".m4a",
".m4b", ".m4r", ".mka", ".mp2", ".mp3", ".mpa", ".oga", ".ogg",
Expand Down
Loading
Loading