Skip to content

feat(ocr): integrate Light-OCR#2005

Open
yyhhyyyyyy wants to merge 28 commits into
devfrom
feat/light-ocr-integration
Open

feat(ocr): integrate Light-OCR#2005
yyhhyyyyyy wants to merge 28 commits into
devfrom
feat/light-ocr-integration

Conversation

@yyhhyyyyyy

@yyhhyyyyyy yyhhyyyyyy commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Integrate @arcships/light-ocr into DeepChat to provide fully offline OCR for image attachments, especially when the selected model does not support vision.

What changed

  • Add main-owned attachment capability routing for vision and non-vision models.
  • Add Auto, Send image and Use OCR text attachment preferences.
  • Persist the exact attachment representation sent to the model.
  • Reuse OCR snapshots for history, retry, edit and compaction.
  • Add submission-scoped OCR preparation cancellation.
  • Preserve Composer drafts across cancellation, failure and session switching.
  • Add encrypted derived OCR caching with TTL, LRU GC and singleflight.
  • Add OCR runtime status, backend selection and cache controls.
  • Localize OCR flows across all 20 shipped locales.

Runtime and packaging

  • Pin @arcships/light-ocr@0.3.0.
  • Pin model bundle ppocrv6-small-native-20260719.1.
  • Run OCR in bundled Node 24.14.1.
  • Verify the Node executable version and SHA-256 during install, afterPack and packaged smoke.
  • Bundle all OCR assets for completely offline use.
  • Support macOS arm64/x64, Windows x64 and Linux x64.
  • Keep Windows arm64 and Linux arm64 explicitly unavailable until compatible upstream assets pass packaged smoke.

Safety and reliability

  • Treat OCR text as untrusted user attachment data.
  • Bound and truncate OCR text before persistence and provider input.
  • Never log OCR text, source paths or content hashes.
  • Launch the helper with a minimal environment allowlist.
  • Run packaged smoke under OS-level network isolation.
  • Ensure preparation cancellation never stops unrelated generation.
  • Safely degrade malformed or legacy attachment metadata.

Scope

Included:

  • Chat user image attachments.
  • Direct send, new thread, queue, steer, remote, retry and compaction.
  • Offline packaged OCR runtime.
  • Runtime observability and cache management.

Not included:

  • Scanned PDF OCR.
  • Knowledge-base image ingestion.
  • MCP, tool or generated images.
  • Automatic model switching.
  • Runtime model downloads.
  • OCR language selection.

Validation

  • Main: 4,477 tests passed, 230 skipped.
  • Renderer: 1,400 tests passed.
  • The remaining 15 renderer failures are pre-existing App.startup.test.ts mock failures outside this diff.
  • Typecheck, i18n, lint, format check and production build passed.
  • macOS arm64 packaged OCR passed with network access denied.
  • Cold recognition: approximately 1.67 seconds.
  • Warm recognition: approximately 27 milliseconds.
  • Peak helper RSS: 509.94 MiB.
  • macOS arm64 zip increase: 65.12 MiB, below the 90 MiB budget.
  • Cross-platform packaged workflows are configured for macOS x64, Windows x64 and Linux x64.

Summary by CodeRabbit

  • New Features
    • Added offline OCR for image attachments, including auto-extraction for non-vision models and send-mode selection (image / OCR text / send without image content).
    • Added an OCR settings page with runtime availability, engine details, and derived OCR cache management (including clearing).
    • Added OCR previews plus blocked/unavailable attachment UI with retry, fallback, and optional model switching.
    • Added submission and attachment-preparation cancellation support.
  • Bug Fixes
    • Improved search, tape recall, and exports to include OCR-derived text.
    • Strengthened packaged OCR integrity verification and introduced installer size regression enforcement for more reliable releases.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds an offline Light OCR attachment pipeline: a helper process/protocol, extraction service, encrypted cache, and capability router, integrated into session/turn coordination, attachment preflight/blocking UI, OCR settings, CI packaging/size gates, shared contracts, and full localization. It also refreshes the ACP registry and model catalog (unrelated).

Changes

Offline Light OCR Attachment Integration

Layer / File(s) Summary
Docs & specs
docs/features/light-ocr-integration/*, docs/issues/light-ocr-follow-up-hardening/spec.md
Adds plan, spec, tasks, and deferred-hardening documentation for the OCR feature.
CI/CD & packaging config
.github/actions/light-ocr-package-size/action.yml, .github/workflows/build.yml, .github/workflows/release.yml, electron-builder.yml, electron.vite.config.ts, package.json, resources/light-ocr-size-budgets.json, resources/runtime-versions.json, tsconfig.node.json, scripts/apple-notarization.js, scripts/notarize-dmg.js, scripts/notarize.js
Adds size-budget enforcement action, network-isolated smoke verification steps, token/permission hardening, packaging config for the OCR helper/native module, DMG notarization hardening, and pinned runtime manifests.
Runtime install & packaging scripts
scripts/install-runtime.mjs, scripts/afterPack.js, scripts/compare-light-ocr-package-size.mjs, scripts/smoke-light-ocr.js, src/main/ocr/lightOcrNativePayload.ts, test/fixtures/light-ocr/*, test/main/scripts/*, test/main/ocr/lightOcrNativePayload.test.ts
Implements runtime installation, OCR asset packaging/verification and native payload materialization, size comparison, and packaged smoke-testing plus fixtures/tests.
Shared attachment types & contracts
src/shared/types/attachment.ts, src/shared/contracts/*, src/shared/utils/attachmentRepresentation.ts, src/shared/settingsNavigation.ts, test/main/shared/*
Defines attachment representation/preparation types, Zod route/settings contracts, and normalization helpers.
OCR core engine & capability router
src/main/ocr/*, test/main/ocr/*
Implements the protocol, helper/process host, image preprocessing, extraction service, artifact cache, scheduler, asset resolver/service, settings, routes, and attachment capability router.
Session/turn coordinator & pending-input integration
src/main/agent/*, src/main/session/*, src/main/app/*, test/main/agent/*, test/main/session/*, test/main/routes/*
Wires attachment preflight into turn coordination, chat service, session lifecycle/routes, blocked pending-input persistence, and submission cancellation.
Context builder, exporter, transcript & tape integration
src/main/agent/deepchat/runtime/contextBuilder.ts, src/main/exporter/*, src/main/session/data/transcript.ts, src/main/tape/*, src/main/remote/*, src/main/lightOcrHelperEntry.ts
Materializes resolved OCR/image representations into model context, exports, search text, and tape recall.
Renderer API clients
src/renderer/api/*
Adds OCR client and submission cancellation/blocked-resolution support to chat/session clients.
Renderer OCR settings UI
src/renderer/settings/components/OcrSettings.vue, src/renderer/settings/settingsRouteComponents.ts
Adds the OCR automation/backend/cache management settings page.
Renderer composer/attachment UI components
src/renderer/src/components/chat/*, src/shadcn/components/ui/dropdown-menu/DropdownMenuContent.vue, src/renderer/src/lib/errors.ts
Adds attachment-preparation dialog, OCR badges/preview, editable input states, and blocked-lane actions.
Composer submit & session store logic
src/renderer/src/features/chat-page/*, src/renderer/src/pages/NewThreadPage.vue, src/renderer/src/stores/ui/*
Reworks submission dispatch/draft state, retry fallback, and pending-input/session store behavior.
i18n translations
src/renderer/src/i18n/*/chat.json, .../routes.json, .../settings.json
Adds localized strings for attachment representation and OCR settings across all locales.

Unrelated ACP Registry & Model Catalog Refresh

Layer / File(s) Summary
Registry & model catalog updates
resources/acp-registry/registry.json, resources/model-db/providers.json
Bumps ACP agent versions/artifacts and refreshes model catalog entries.

Estimated code review effort: 5 (Critical) | ~180 minutes

Possibly related PRs

Suggested reviewers: zhangmo8, zerob13

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: integrating Light-OCR/OCR support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/light-ocr-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (5)
src/renderer/src/components/chat/nodes/fileAttachment.ts (1)

41-46: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Validate parsed requestedRepresentation with the shared normalizer.

parseHTML only defaults to 'auto' when the attribute is missing, not when it holds an invalid value. normalizeAttachmentRepresentationPreference (src/shared/utils/attachmentRepresentation.ts) already exists to validate/coerce this exact preference and is presumably relied on elsewhere in the attachment contract chain; using it here keeps deserialized HTML consistent with the rest of the pipeline.

♻️ Proposed fix
+import { normalizeAttachmentRepresentationPreference } from '`@shared/utils/attachmentRepresentation`'
...
       requestedRepresentation: {
         default: 'auto',
-        parseHTML: (el) => el.getAttribute('data-requested-representation') || 'auto',
+        parseHTML: (el) =>
+          normalizeAttachmentRepresentationPreference(
+            el.getAttribute('data-requested-representation')
+          ) ?? 'auto',
         renderHTML: (attrs) => ({
           'data-requested-representation': attrs.requestedRepresentation
         })
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/components/chat/nodes/fileAttachment.ts` around lines 41 -
46, Update the requestedRepresentation parseHTML handler in the attachment node
to pass the retrieved data-requested-representation value through
normalizeAttachmentRepresentationPreference, preserving 'auto' as the fallback
for missing or invalid values. Keep renderHTML unchanged.
test/main/session/chatService.test.ts (1)

178-281: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Submission-scoped abort is tested for sendMessage but not for steerActiveTurn.

L208-247 only covers stopStream-triggered abort for steerActiveTurn; L249-281 covers submission-scoped AbortController abort, but only for sendMessage. Consider adding an analogous submission-scoped-abort test for steerActiveTurn to confirm it doesn't inadvertently cancel generation/session permissions either.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/session/chatService.test.ts` around lines 178 - 281, Add a
submission-scoped AbortController test for steerActiveTurn, analogous to the
existing sendMessage test. Keep the steer operation pending until the supplied
signal aborts, then assert it rejects with AbortError without calling
turn.cancelGeneration or sessionPermissionPort.clearSessionPermissions.
test/main/routes/dispatcher.test.ts (2)

4255-4323: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Renderer-ownership cancellation coverage exists only for chat.sendMessage.

This is a strong regression test for a real authorization boundary (blocking cross-renderer cancellation), but there's no analogous test exercising chat.cancelSubmission against an in-flight chat.steerActiveTurn or sessions.retryMessage attachment preflight, even though both now surface attachmentPreparation/blocking behavior in this same file. Consider extending this pattern to those two paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/routes/dispatcher.test.ts` around lines 4255 - 4323, Extend the
renderer-ownership cancellation coverage from the existing chat.sendMessage test
to both in-flight chat.steerActiveTurn and sessions.retryMessage
attachment-preflight flows. For each path, await the preflight becoming blocked,
verify chat.cancelSubmission from a different renderer returns { cancelled:
false } without aborting it, then verify cancellation from the owning renderer
succeeds and the operation rejects with AbortError; preserve the existing
post-cancellation false result.

1-1: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Submission-scoped cancellation is only fully tested for chat.sendMessage, not steerActiveTurn. Both files establish a solid pattern for verifying the new abort/attachment-preflight semantics, but the pattern isn't extended to the steer path at either layer.

  • test/main/routes/dispatcher.test.ts#L4255-4323: add an analogous renderer-ownership chat.cancelSubmission test for an in-flight chat.steerActiveTurn (and ideally sessions.retryMessage, pending the separate signal-wiring verification) attachment preflight.
  • test/main/session/chatService.test.ts#L178-281: add a submission-scoped AbortController abort test for steerActiveTurn (mirroring the existing sendMessage test at L249-281), distinct from the already-covered stopStream-triggered abort at L208-247.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/routes/dispatcher.test.ts` at line 1, Extend the existing
cancellation coverage to steerActiveTurn: in the dispatcher tests, add a
renderer-ownership chat.cancelSubmission case for an in-flight steerActiveTurn
attachment preflight, preserving the established sendMessage pattern; in
chatService tests, add a submission-scoped AbortController abort test for
steerActiveTurn, separate from the stopStream-triggered abort coverage. Also
apply the analogous renderer test to sessions.retryMessage if its signal wiring
is available.
test/main/session/data/tables/deepchatPendingInputsTable.test.ts (1)

157-183: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add a negative-case test for invalid/forged resolvedRepresentation.

This test only verifies the happy path where a valid resolvedRepresentation is preserved. Given attachment data is treated as untrusted (per PR objectives) and this is the boundary where renderer-supplied payloads enter persisted pending-input state, add a companion case asserting that a forged/invalid resolvedRepresentation (e.g., unexpected kind, extra fields, or a fake kind: 'image' used to bypass OCR) is rejected or stripped, mirroring how test 6 (lines 134-155) verifies blocking metadata is sanitized.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/session/data/tables/deepchatPendingInputsTable.test.ts` around
lines 157 - 183, Add a companion negative-case test near the existing valid
snapshot test for createQueueInput, supplying a forged or invalid
resolvedRepresentation such as an unsupported kind or fake image representation
with extra fields. Assert that the persisted pending payload rejects or strips
the invalid representation, matching the sanitization expectations established
by test 6.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@resources/model-db/providers.json`:
- Line 260229: Update the context value for the Meituan: LongCat 2.0 provider
entry from 1048756 to 1048576 in providers.json, leaving all other provider
configuration unchanged.
- Around line 236569-236573: The gemini-3.6-flash pricing entry has an incorrect
cache_read value. In its cost object, update cache_read from 1.5 to 0.15 while
preserving the input and output values.
- Around line 259585-259661: Update the `type` field in both OpenRouter entries,
`google/gemini-3.5-flash-lite` and `google/gemini-3.6-flash`, from
`imageGeneration` to `chat`; leave their existing modalities, reasoning, limits,
and other capabilities unchanged.

In `@src/main/agent/deepchat/runtime/contextBuilder.ts`:
- Around line 407-428: Update buildResolvedImageRepresentationContext to inspect
the ocr_text representation’s truncated flag and append a clear truncation note
to the generated OCR context when it is true. Preserve the existing unavailable,
empty-text, escaping, metadata, and non-truncated output behavior.
- Around line 430-432: Update escapeUntrustedOcrBlockDelimiters to escape every
angle bracket in the input, rather than pattern-matching only untrusted_ocr_data
tags. Ensure both “<” and “>” are encoded so any OCR text resembling an
HTML-like delimiter cannot bypass the trust boundary.

In `@src/main/ocr/ocrRuntimeAssetResolver.ts`:
- Around line 256-262: Update the escape guard in the runtime asset path
validation to also reject the exact parent-relative value (`relative === '..'`),
while preserving the existing checks for empty, nested-parent, and absolute
paths. Keep the change scoped to the validation logic surrounding `resolvedRoot`
and `resolvedPath`.

In `@src/main/session/data/pendingInputStore.ts`:
- Around line 145-154: Update updateQueueInput to validate the current row.state
before writing payload_json, matching the state-guard behavior of the sibling
mutators. Reject claimed or otherwise non-editable states before mutation, while
preserving the existing blocked-to-pending reset and normal update behavior for
allowed states.

In `@src/main/session/turn.ts`:
- Around line 281-287: In the ACP branch of the turn handling flow, move
commitRetryMessage so it runs only after runtime.send completes successfully.
Preserve the existing sessionId and prepared.sourceOrderSeq arguments, and
ensure send errors propagate without committing or deleting the prior
transcript.

In `@src/renderer/settings/components/OcrSettings.vue`:
- Line 256: Update the status polling error handling around statusErrorNotified
and the logic near status.value so failures after a prior successful poll are
surfaced instead of remaining silent; preserve deduplication by notifying only
on the first failure in each failure period, and reset that failure state when a
subsequent poll succeeds. Ensure the status card no longer presents stale
available data without an indication of polling failure.

In `@src/renderer/src/components/chat/ChatInputBox.vue`:
- Around line 558-563: Update handleKeydown so the non-editable guard does not
prevent Tab navigation or standard copy/select-all shortcuts (Ctrl/Cmd+Tab,
Ctrl/Cmd+C, and Ctrl/Cmd+A as applicable); allow those key-specific paths to
execute before returning for other editing commands. Preserve the existing
prevention behavior for actions that modify the draft while props.editable is
false.

In `@src/renderer/src/components/message/MessageItemUser.vue`:
- Around line 256-263: Update messageFileByKey and its fallback lookup in
MessageItemUser so exact file paths remain uniquely mapped, while basename
matching is only used when exactly one attachment has that name. Do not
overwrite basename candidates when duplicates exist; treat ambiguous names as
unmatched so inline blocks cannot receive incorrect file metadata or OCR
content.

In `@src/renderer/src/i18n/he-IL/chat.json`:
- Line 431: Update the Hebrew translations for image_limit_exceeded and the
corresponding message at line 440 to replace התור with a term clearly meaning
the current turn or round, while preserving the existing message meaning and
formatting.

In `@test/main/ocr/ocrArtifactStore.test.ts`:
- Around line 15-26: Ensure encrypted persistence tests cannot be silently
skipped in required CI: update the OCR artifact store test setup around
sqliteAvailable and persistentIt to honor DEEPCHAT_REQUIRE_NATIVE_SQLITE,
failing when the flag is set but better-sqlite3-multiple-ciphers is unavailable;
also configure that environment variable for the CI command running these tests
if needed.

---

Nitpick comments:
In `@src/renderer/src/components/chat/nodes/fileAttachment.ts`:
- Around line 41-46: Update the requestedRepresentation parseHTML handler in the
attachment node to pass the retrieved data-requested-representation value
through normalizeAttachmentRepresentationPreference, preserving 'auto' as the
fallback for missing or invalid values. Keep renderHTML unchanged.

In `@test/main/routes/dispatcher.test.ts`:
- Around line 4255-4323: Extend the renderer-ownership cancellation coverage
from the existing chat.sendMessage test to both in-flight chat.steerActiveTurn
and sessions.retryMessage attachment-preflight flows. For each path, await the
preflight becoming blocked, verify chat.cancelSubmission from a different
renderer returns { cancelled: false } without aborting it, then verify
cancellation from the owning renderer succeeds and the operation rejects with
AbortError; preserve the existing post-cancellation false result.
- Line 1: Extend the existing cancellation coverage to steerActiveTurn: in the
dispatcher tests, add a renderer-ownership chat.cancelSubmission case for an
in-flight steerActiveTurn attachment preflight, preserving the established
sendMessage pattern; in chatService tests, add a submission-scoped
AbortController abort test for steerActiveTurn, separate from the
stopStream-triggered abort coverage. Also apply the analogous renderer test to
sessions.retryMessage if its signal wiring is available.

In `@test/main/session/chatService.test.ts`:
- Around line 178-281: Add a submission-scoped AbortController test for
steerActiveTurn, analogous to the existing sendMessage test. Keep the steer
operation pending until the supplied signal aborts, then assert it rejects with
AbortError without calling turn.cancelGeneration or
sessionPermissionPort.clearSessionPermissions.

In `@test/main/session/data/tables/deepchatPendingInputsTable.test.ts`:
- Around line 157-183: Add a companion negative-case test near the existing
valid snapshot test for createQueueInput, supplying a forged or invalid
resolvedRepresentation such as an unsupported kind or fake image representation
with extra fields. Assert that the persisted pending payload rejects or strips
the invalid representation, matching the sanitization expectations established
by test 6.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee0267a3-1fb3-4f16-b76a-a4ee94389d34

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6852b and 420027e.

⛔ Files ignored due to path filters (2)
  • src/renderer/src/lib/icons/icon-collections.generated.ts is excluded by !**/*.generated.*
  • src/renderer/src/lib/icons/icon-whitelist.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (234)
  • .github/actions/light-ocr-package-size/action.yml
  • .github/workflows/build.yml
  • .github/workflows/release.yml
  • docs/features/light-ocr-integration/plan.md
  • docs/features/light-ocr-integration/spec.md
  • docs/features/light-ocr-integration/tasks.md
  • docs/issues/light-ocr-follow-up-hardening/spec.md
  • electron-builder.yml
  • electron.vite.config.ts
  • package.json
  • resources/acp-registry/registry.json
  • resources/light-ocr-size-budgets.json
  • resources/model-db/providers.json
  • resources/runtime-versions.json
  • scripts/afterPack.js
  • scripts/compare-light-ocr-package-size.mjs
  • scripts/install-runtime.mjs
  • scripts/smoke-light-ocr.js
  • src/main/agent/deepchat/runtime/contextBuilder.ts
  • src/main/agent/deepchat/runtime/deepChatRuntimeCoordinator.ts
  • src/main/agent/deepchat/runtime/turnCoordinator.ts
  • src/main/agent/manager/deepChatAgentBackend.ts
  • src/main/agent/manager/directAcpAgentBackend.ts
  • src/main/agent/manager/sessionHandles.ts
  • src/main/agent/shared/agentSessionHandle.ts
  • src/main/agent/shared/agentSessionNormalization.ts
  • src/main/app/composition.ts
  • src/main/app/settingsRoutes.ts
  • src/main/data/databaseConnection.ts
  • src/main/data/schemaCatalog.ts
  • src/main/exporter/agentSessionExporter.ts
  • src/main/exporter/formats/conversationExporter.ts
  • src/main/exporter/formats/nowledgeMemExporter.ts
  • src/main/exporter/formats/userMessageText.ts
  • src/main/lightOcrHelperEntry.ts
  • src/main/ocr/attachmentCapabilityRouter.ts
  • src/main/ocr/imagePreprocessor.ts
  • src/main/ocr/imageTextExtractionService.ts
  • src/main/ocr/lightOcrHelper.ts
  • src/main/ocr/lightOcrProcessHost.ts
  • src/main/ocr/lightOcrProtocol.ts
  • src/main/ocr/ocrArtifactStore.ts
  • src/main/ocr/ocrCacheKeyProvider.ts
  • src/main/ocr/ocrExtractionScheduler.ts
  • src/main/ocr/ocrRuntimeAssetResolver.ts
  • src/main/ocr/ocrRuntimeService.ts
  • src/main/ocr/ocrSettings.ts
  • src/main/ocr/routes.ts
  • src/main/remote/conversation/runner.ts
  • src/main/session/chatService.ts
  • src/main/session/contracts.ts
  • src/main/session/data/contracts.ts
  • src/main/session/data/pendingInputStore.ts
  • src/main/session/data/pendingInputs.ts
  • src/main/session/data/tables/deepchatPendingInputs.ts
  • src/main/session/data/transcript.ts
  • src/main/session/data/userMessageContent.ts
  • src/main/session/lifecycle.ts
  • src/main/session/routes.ts
  • src/main/session/sessionService.ts
  • src/main/session/submissionCancellationRegistry.ts
  • src/main/session/transcriptMutations.ts
  • src/main/session/turn.ts
  • src/main/tape/application/recallProjection.ts
  • src/main/tape/application/recallService.ts
  • src/main/tape/infrastructure/sqlite/tapeSearchProjectionStore.ts
  • src/renderer/api/ChatClient.ts
  • src/renderer/api/OcrClient.ts
  • src/renderer/api/SessionClient.ts
  • src/renderer/settings/components/OcrSettings.vue
  • src/renderer/settings/settingsRouteComponents.ts
  • src/renderer/src/components/chat/AttachmentPreparationDialog.vue
  • src/renderer/src/components/chat/ChatAttachmentItem.vue
  • src/renderer/src/components/chat/ChatInputBox.vue
  • src/renderer/src/components/chat/ChatInputToolbar.vue
  • src/renderer/src/components/chat/ChatStatusBar.vue
  • src/renderer/src/components/chat/PendingInputLane.vue
  • src/renderer/src/components/chat/composables/useChatInputFiles.ts
  • src/renderer/src/components/chat/nodes/FileAttachmentView.vue
  • src/renderer/src/components/chat/nodes/fileAttachment.ts
  • src/renderer/src/components/chat/nodes/symbols.ts
  • src/renderer/src/components/message/MessageContent.vue
  • src/renderer/src/components/message/MessageItemUser.vue
  • src/renderer/src/features/chat-page/ChatPage.vue
  • src/renderer/src/features/chat-page/composables/useComposerSubmit.ts
  • src/renderer/src/features/chat-page/composables/useMessageActions.ts
  • src/renderer/src/features/chat-page/composables/usePendingInputActions.ts
  • src/renderer/src/features/chat-page/model/composerDraftState.ts
  • src/renderer/src/features/chat-page/model/displayMessage.ts
  • src/renderer/src/i18n/da-DK/chat.json
  • src/renderer/src/i18n/da-DK/routes.json
  • src/renderer/src/i18n/da-DK/settings.json
  • src/renderer/src/i18n/de-DE/chat.json
  • src/renderer/src/i18n/de-DE/routes.json
  • src/renderer/src/i18n/de-DE/settings.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/en-US/routes.json
  • src/renderer/src/i18n/en-US/settings.json
  • src/renderer/src/i18n/es-ES/chat.json
  • src/renderer/src/i18n/es-ES/routes.json
  • src/renderer/src/i18n/es-ES/settings.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fa-IR/routes.json
  • src/renderer/src/i18n/fa-IR/settings.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/fr-FR/routes.json
  • src/renderer/src/i18n/fr-FR/settings.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/he-IL/routes.json
  • src/renderer/src/i18n/he-IL/settings.json
  • src/renderer/src/i18n/id-ID/chat.json
  • src/renderer/src/i18n/id-ID/routes.json
  • src/renderer/src/i18n/id-ID/settings.json
  • src/renderer/src/i18n/it-IT/chat.json
  • src/renderer/src/i18n/it-IT/routes.json
  • src/renderer/src/i18n/it-IT/settings.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/ja-JP/routes.json
  • src/renderer/src/i18n/ja-JP/settings.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/renderer/src/i18n/ko-KR/routes.json
  • src/renderer/src/i18n/ko-KR/settings.json
  • src/renderer/src/i18n/ms-MY/chat.json
  • src/renderer/src/i18n/ms-MY/routes.json
  • src/renderer/src/i18n/ms-MY/settings.json
  • src/renderer/src/i18n/pl-PL/chat.json
  • src/renderer/src/i18n/pl-PL/routes.json
  • src/renderer/src/i18n/pl-PL/settings.json
  • src/renderer/src/i18n/pt-BR/chat.json
  • src/renderer/src/i18n/pt-BR/routes.json
  • src/renderer/src/i18n/pt-BR/settings.json
  • src/renderer/src/i18n/ru-RU/chat.json
  • src/renderer/src/i18n/ru-RU/routes.json
  • src/renderer/src/i18n/ru-RU/settings.json
  • src/renderer/src/i18n/tr-TR/chat.json
  • src/renderer/src/i18n/tr-TR/routes.json
  • src/renderer/src/i18n/tr-TR/settings.json
  • src/renderer/src/i18n/vi-VN/chat.json
  • src/renderer/src/i18n/vi-VN/routes.json
  • src/renderer/src/i18n/vi-VN/settings.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/zh-CN/routes.json
  • src/renderer/src/i18n/zh-CN/settings.json
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/zh-HK/routes.json
  • src/renderer/src/i18n/zh-HK/settings.json
  • src/renderer/src/i18n/zh-TW/chat.json
  • src/renderer/src/i18n/zh-TW/routes.json
  • src/renderer/src/i18n/zh-TW/settings.json
  • src/renderer/src/lib/errors.ts
  • src/renderer/src/pages/NewThreadPage.vue
  • src/renderer/src/stores/ui/attachmentPreparation.ts
  • src/renderer/src/stores/ui/pendingInput.ts
  • src/renderer/src/stores/ui/session.ts
  • src/shadcn/components/ui/dropdown-menu/DropdownMenuContent.vue
  • src/shared/chat.d.ts
  • src/shared/contracts/common.ts
  • src/shared/contracts/domainSchemas.ts
  • src/shared/contracts/events/settings.events.ts
  • src/shared/contracts/routes.ts
  • src/shared/contracts/routes/chat.routes.ts
  • src/shared/contracts/routes/ocr.routes.ts
  • src/shared/contracts/routes/sessions.routes.ts
  • src/shared/contracts/routes/settings.routes.ts
  • src/shared/contracts/routes/system.routes.ts
  • src/shared/settingsNavigation.ts
  • src/shared/types/agent-interface.d.ts
  • src/shared/types/attachment.ts
  • src/shared/types/core/chat.ts
  • src/shared/utils/attachmentRepresentation.ts
  • test/fixtures/light-ocr/fake-helper.mjs
  • test/main/agent/deepchat/runtime/contextBuilder.test.ts
  • test/main/agent/deepchat/runtime/deepChatRuntimeCoordinator.test.ts
  • test/main/agent/manager/deepChatAgentBackend.test.ts
  • test/main/agent/manager/directAcpAgentBackend.test.ts
  • test/main/data/databaseConnection.test.ts
  • test/main/exporter/agentSessionExporter.test.ts
  • test/main/memory/memoryNativeMigration.test.ts
  • test/main/ocr/attachmentCapabilityRouter.test.ts
  • test/main/ocr/imagePreprocessor.test.ts
  • test/main/ocr/imageTextExtractionService.test.ts
  • test/main/ocr/lightOcrHelper.test.ts
  • test/main/ocr/lightOcrProcessHost.test.ts
  • test/main/ocr/ocrArtifactStore.test.ts
  • test/main/ocr/ocrCacheKeyProvider.test.ts
  • test/main/ocr/ocrExtractionScheduler.test.ts
  • test/main/ocr/ocrRuntimeAssetResolver.test.ts
  • test/main/ocr/ocrRuntimeService.test.ts
  • test/main/ocr/ocrSettings.test.ts
  • test/main/ocr/routes.test.ts
  • test/main/remote/remoteConversationRunner.test.ts
  • test/main/routes/contracts.test.ts
  • test/main/routes/dispatcher.test.ts
  • test/main/scripts/afterPack.test.ts
  • test/main/scripts/installRuntime.test.ts
  • test/main/scripts/lightOcrPackageSize.test.ts
  • test/main/scripts/smokeLightOcr.test.ts
  • test/main/session/assignmentPolicy.test.ts
  • test/main/session/chatService.test.ts
  • test/main/session/data/pendingInputStore.test.ts
  • test/main/session/data/pendingInputs.test.ts
  • test/main/session/data/tables/deepchatPendingInputsTable.test.ts
  • test/main/session/data/tapeRecall.test.ts
  • test/main/session/data/transcript.test.ts
  • test/main/session/lifecycle.test.ts
  • test/main/session/runtimeIntegration.test.ts
  • test/main/session/session.integration.test.ts
  • test/main/session/sessionFixture.ts
  • test/main/session/submissionCancellationRegistry.test.ts
  • test/main/session/transcriptMutations.test.ts
  • test/main/session/turn.test.ts
  • test/main/shared/attachmentRepresentation.test.ts
  • test/main/shared/settingsNavigation.test.ts
  • test/renderer/api/clients.test.ts
  • test/renderer/components/AttachmentPreparationDialog.test.ts
  • test/renderer/components/ChatAttachmentItem.test.ts
  • test/renderer/components/ChatInputBox.test.ts
  • test/renderer/components/ChatInputToolbar.test.ts
  • test/renderer/components/ChatPage.test.ts
  • test/renderer/components/DropdownMenuContent.test.ts
  • test/renderer/components/NewThreadPage.onboarding.test.ts
  • test/renderer/components/NewThreadPage.test.ts
  • test/renderer/components/OcrSettings.test.ts
  • test/renderer/components/PendingInputLane.test.ts
  • test/renderer/components/useChatInputFiles.test.ts
  • test/renderer/features/chat-page/composables/useComposerSubmit.test.ts
  • test/renderer/features/chat-page/composables/useMessageActions.test.ts
  • test/renderer/features/chat-page/composables/usePendingInputActions.test.ts
  • test/renderer/features/chat-page/model/composerDraftState.test.ts
  • test/renderer/lib/errors.test.ts
  • test/renderer/pages/NewThreadPage.test.ts
  • test/renderer/stores/attachmentPreparationStore.test.ts
  • test/renderer/stores/sessionStore.test.ts
  • tsconfig.node.json

Comment thread resources/model-db/providers.json
Comment thread resources/model-db/providers.json
]
},
"limit": {
"context": 1048756,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the local context around the flagged line and search for related values.
sed -n '260210,260245p' resources/model-db/providers.json

printf '\n--- occurrences of 1048756 ---\n'
rg -n '"context": 1048756|1048756' resources/model-db/providers.json || true

printf '\n--- occurrences of 1048576 ---\n'
rg -n '"context": 1048576|1048576' resources/model-db/providers.json || true

printf '\n--- nearby LongCat/Anthropic-style entries ---\n'
rg -n 'LongCat|1048576|1048756' resources/model-db/providers.json | head -n 40

Repository: ThinkInAIXYZ/deepchat

Length of output: 19233


🌐 Web query:

LongCat 2.0 context limit 1048756 1048576 provider spec

💡 Result:

LongCat-2.0’s documented context limit is 1,048,576 tokens (1M), not 1,048,756. The provider docs also say the max output length is 131,072 tokens (128K). (long-cat.org)

If you’re mapping this into a provider spec, use:

  • context_window: 1048576
  • max_output_tokens: 131072 (long-cat.org)

If you want, I can format this as JSON/YAML for your spec.

Citations:


Fix the LongCat 2.0 context limit. 1048756 should be 1048576 for Meituan: LongCat 2.0.

🔧 Suggested fix
-            "context": 1048756,
+            "context": 1048576,
📝 Committable suggestion

‼️ 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.

Suggested change
"context": 1048756,
"context": 1048576,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/model-db/providers.json` at line 260229, Update the context value
for the Meituan: LongCat 2.0 provider entry from 1048756 to 1048576 in
providers.json, leaving all other provider configuration unchanged.

Comment on lines +407 to +428
function buildResolvedImageRepresentationContext(files: MessageFile[]): string {
const imageFiles = files.filter((file) => isImageAttachment(file))
return imageFiles
.flatMap((file, index) => {
const resolved = getAttachmentResolvedRepresentation(file)
if (!resolved || resolved.kind === 'image') return []
const fileName = typeof file.name === 'string' ? file.name : `image-${index + 1}`
const mimeType = resolveFileMimeType(file)
const metadata = [`name: ${fileName}`, `mime: ${mimeType}`].join('\n')
if (resolved.kind === 'unavailable') {
return [
`[Attached Image ${index + 1} - content unavailable]\n${metadata}\nreason: ${resolved.reason}`
]
}

const escapedText = escapeUntrustedOcrBlockDelimiters(resolved.text)
return [
`[Attached Image ${index + 1} - OCR text; untrusted attachment data]\n${metadata}\n<untrusted_ocr_data>\n${escapedText || '[empty]'}\n</untrusted_ocr_data>`
]
})
.join('\n\n')
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Truncated OCR text isn't flagged to the model.

resolved of kind 'ocr_text' carries a truncated flag (per the attachment representation contract), but this function never surfaces it. When OCR text was cut short, the model has no signal that the content is incomplete and may treat it as the full image content.

🛠️ Proposed fix: append a truncation note when present
       const escapedText = escapeUntrustedOcrBlockDelimiters(resolved.text)
+      const truncationNote = resolved.truncated ? '\n[text truncated]' : ''
       return [
-        `[Attached Image ${index + 1} - OCR text; untrusted attachment data]\n${metadata}\n<untrusted_ocr_data>\n${escapedText || '[empty]'}\n</untrusted_ocr_data>`
+        `[Attached Image ${index + 1} - OCR text; untrusted attachment data]\n${metadata}\n<untrusted_ocr_data>\n${escapedText || '[empty]'}\n</untrusted_ocr_data>${truncationNote}`
       ]
📝 Committable suggestion

‼️ 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.

Suggested change
function buildResolvedImageRepresentationContext(files: MessageFile[]): string {
const imageFiles = files.filter((file) => isImageAttachment(file))
return imageFiles
.flatMap((file, index) => {
const resolved = getAttachmentResolvedRepresentation(file)
if (!resolved || resolved.kind === 'image') return []
const fileName = typeof file.name === 'string' ? file.name : `image-${index + 1}`
const mimeType = resolveFileMimeType(file)
const metadata = [`name: ${fileName}`, `mime: ${mimeType}`].join('\n')
if (resolved.kind === 'unavailable') {
return [
`[Attached Image ${index + 1} - content unavailable]\n${metadata}\nreason: ${resolved.reason}`
]
}
const escapedText = escapeUntrustedOcrBlockDelimiters(resolved.text)
return [
`[Attached Image ${index + 1} - OCR text; untrusted attachment data]\n${metadata}\n<untrusted_ocr_data>\n${escapedText || '[empty]'}\n</untrusted_ocr_data>`
]
})
.join('\n\n')
}
function buildResolvedImageRepresentationContext(files: MessageFile[]): string {
const imageFiles = files.filter((file) => isImageAttachment(file))
return imageFiles
.flatMap((file, index) => {
const resolved = getAttachmentResolvedRepresentation(file)
if (!resolved || resolved.kind === 'image') return []
const fileName = typeof file.name === 'string' ? file.name : `image-${index + 1}`
const mimeType = resolveFileMimeType(file)
const metadata = [`name: ${fileName}`, `mime: ${mimeType}`].join('\n')
if (resolved.kind === 'unavailable') {
return [
`[Attached Image ${index + 1} - content unavailable]\n${metadata}\nreason: ${resolved.reason}`
]
}
const escapedText = escapeUntrustedOcrBlockDelimiters(resolved.text)
const truncationNote = resolved.truncated ? '\n[text truncated]' : ''
return [
`[Attached Image ${index + 1} - OCR text; untrusted attachment data]\n${metadata}\n<untrusted_ocr_data>\n${escapedText || '[empty]'}\n</untrusted_ocr_data>${truncationNote}`
]
})
.join('\n\n')
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/agent/deepchat/runtime/contextBuilder.ts` around lines 407 - 428,
Update buildResolvedImageRepresentationContext to inspect the ocr_text
representation’s truncated flag and append a clear truncation note to the
generated OCR context when it is true. Preserve the existing unavailable,
empty-text, escaping, metadata, and non-truncated output behavior.

Comment on lines +430 to +432
function escapeUntrustedOcrBlockDelimiters(value: string): string {
return value.replace(/<(\/?untrusted_ocr_data)(?=[\s>])/gi, '&lt;$1')
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Delimiter-escaping regex can be bypassed, weakening the untrusted-OCR trust boundary.

The regex /<(\/?untrusted_ocr_data)(?=[\s>])/gi only matches when < is immediately followed by the tag name. Attacker-controlled OCR text (from a crafted image) can insert whitespace, e.g. < untrusted_ocr_data> or < /untrusted_ocr_data>, to slip past this check while still visually/structurally resembling the delimiter to the model — undermining the purpose of wrapping OCR text as untrusted content. Since the goal here is simply to prevent any <...>-shaped token from being mistaken for the trust-boundary tag, escaping every < unconditionally is both simpler and closes this gap.

🔒 Proposed fix: escape all angle brackets instead of pattern-matching the tag name
 function escapeUntrustedOcrBlockDelimiters(value: string): string {
-  return value.replace(/<(\/?untrusted_ocr_data)(?=[\s>])/gi, '&lt;$1')
+  return value.replace(/</g, '&lt;')
 }
📝 Committable suggestion

‼️ 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.

Suggested change
function escapeUntrustedOcrBlockDelimiters(value: string): string {
return value.replace(/<(\/?untrusted_ocr_data)(?=[\s>])/gi, '&lt;$1')
}
function escapeUntrustedOcrBlockDelimiters(value: string): string {
return value.replace(/</g, '&lt;')
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 430-430: Avoid hand-rolled HTML escaping (replacing characters with HTML entities); use a vetted encoder/sanitizer such as DOMPurify or sanitize-html.
Context: value.replace(/<(/?untrusted_ocr_data)(?=[\s>])/gi, '<$1')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').

(manual-sanitization-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/agent/deepchat/runtime/contextBuilder.ts` around lines 430 - 432,
Update escapeUntrustedOcrBlockDelimiters to escape every angle bracket in the
input, rather than pattern-matching only untrusted_ocr_data tags. Ensure both
“<” and “>” are encoded so any OCR text resembling an HTML-like delimiter cannot
bypass the trust boundary.

Source: Linters/SAST tools

const statusLoading = ref(false)
const cacheClearInFlight = ref(false)
const clearDialogOpen = ref(false)
const statusErrorNotified = ref(false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Repeated status-poll failures after first success stay silent forever.

statusErrorNotified blocks the failure toast whenever status.value is already set (line 381), and it's only reset to false on success (line 379). Once the initial load succeeds, subsequent polling failures (e.g., the OCR runtime process crashing) never surface any indication — the panel keeps showing stale "available" data indefinitely.

🩹 Suggested approach
   try {
     status.value = await ocrClient.getRuntimeStatus()
     statusErrorNotified.value = false
   } catch {
-    if (!status.value && !statusErrorNotified.value) {
+    if (!statusErrorNotified.value) {
       statusErrorNotified.value = true
       showFailure('settings.ocr.statusLoadFailed')
     }
   } finally {

Alternatively, surface a lightweight "stale" indicator on the status card instead of a toast on every repeated failure.

Also applies to: 374-388

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/settings/components/OcrSettings.vue` at line 256, Update the
status polling error handling around statusErrorNotified and the logic near
status.value so failures after a prior successful poll are surfaced instead of
remaining silent; preserve deduplication by notifying only on the first failure
in each failure period, and reset that failure state when a subsequent poll
succeeds. Ensure the status card no longer presents stale available data without
an indication of polling failure.

Comment on lines 558 to +563
function handleKeydown(e: KeyboardEvent) {
if (!props.editable) {
e.preventDefault()
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Non-editable guard blocks copy and Tab-out, not just editing.

if (!props.editable) { e.preventDefault(); return } runs before any key-specific logic, so while the box is temporarily non-editable (attachment preparation/blocked state) it also blocks Ctrl/Cmd+C, Ctrl/Cmd+A, and Tab — preventing users from copying their draft text or tabbing focus away, effectively trapping keyboard focus in a disabled input.

🐛 Proposed fix to allow navigation/copy through
 function handleKeydown(e: KeyboardEvent) {
   if (!props.editable) {
-    e.preventDefault()
-    return
+    const isCopyOrSelectAll =
+      (e.metaKey || e.ctrlKey) && ['c', 'a'].includes(e.key.toLowerCase())
+    const isNavigationKey = [
+      'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'Tab', 'Escape'
+    ].includes(e.key)
+    if (!isCopyOrSelectAll && !isNavigationKey) {
+      e.preventDefault()
+    }
+    return
   }
📝 Committable suggestion

‼️ 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.

Suggested change
function handleKeydown(e: KeyboardEvent) {
if (!props.editable) {
e.preventDefault()
return
}
function handleKeydown(e: KeyboardEvent) {
if (!props.editable) {
const isCopyOrSelectAll = (e.metaKey || e.ctrlKey) && ['c', 'a'].includes(e.key.toLowerCase())
const isNavigationKey = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'Tab', 'Escape'].includes(e.key)
if (!isCopyOrSelectAll && !isNavigationKey) {
e.preventDefault()
}
return
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/components/chat/ChatInputBox.vue` around lines 558 - 563,
Update handleKeydown so the non-editable guard does not prevent Tab navigation
or standard copy/select-all shortcuts (Ctrl/Cmd+Tab, Ctrl/Cmd+C, and Ctrl/Cmd+A
as applicable); allow those key-specific paths to execute before returning for
other editing commands. Preserve the existing prevention behavior for actions
that modify the draft while props.editable is false.

Comment thread src/renderer/src/components/message/MessageItemUser.vue Outdated
"reasons": {
"automatic_ocr_disabled": "OCR אוטומטי כבוי.",
"image_dimensions_exceeded": "ממדי התמונה המפוענחת חורגים ממגבלת הבטיחות.",
"image_limit_exceeded": "התור הזה מכיל יותר תמונות מכפי שמנוע ה־OCR יכול לעבד.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a turn-specific Hebrew term instead of “queue”.

Lines [431] and [440] translate the current turn as התור (“the queue”), which can be confused with the pending-input queue. Use wording that clearly means the current turn/round.

Also applies to: 440-440

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/i18n/he-IL/chat.json` at line 431, Update the Hebrew
translations for image_limit_exceeded and the corresponding message at line 440
to replace התור with a term clearly meaning the current turn or round, while
preserving the existing message meaning and formatting.

Comment on lines +15 to +26
const sqliteModule = await import('better-sqlite3-multiple-ciphers').catch(() => null)
let sqliteAvailable = false
if (sqliteModule) {
try {
const smokeDatabase = new sqliteModule.default(':memory:')
smokeDatabase.close()
sqliteAvailable = true
} catch {
sqliteAvailable = false
}
}
const persistentIt = sqliteAvailable ? it : it.skip

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether CI/test setup forces the native module to be present, or allows silent skip.
fd -t f 'vitest*.config*' -x cat {}
rg -nC3 'better-sqlite3-multiple-ciphers' package.json
rg -nC3 -- 'sqliteAvailable|persistentIt' test/main/ocr/ocrArtifactStore.test.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 8738


🏁 Script executed:

#!/bin/bash
# Search for CI configuration files
find . -type f \( -name '.gitlab-ci.yml' -o -name '*.yml' -o -name '*.yaml' \) -path '*/.github/workflows/*' | head -20
cat .github/workflows/*.yml 2>/dev/null | head -200
# Check test setup files
cat test/setup.ts 2>/dev/null

Repository: ThinkInAIXYZ/deepchat

Length of output: 15808


🏁 Script executed:

#!/bin/bash
# Look for build/installation scripts and README hints
cat package.json | grep -A5 -B5 '"scripts"'
cat README.md 2>/dev/null | head -100
# Check if there are any environment variable checks or CI-specific behavior
rg -n 'CI|BUILD|NODE_ENV|GITHUB_' test/main/ocr/ocrArtifactStore.test.ts test/setup.ts

Repository: ThinkInAIXYZ/deepchat

Length of output: 7432


🏁 Script executed:

#!/bin/bash
# Check how persistentIt is actually used and whether failures are expected
rg -B5 -A15 'persistentIt' test/main/ocr/ocrArtifactStore.test.ts | head -80
# Look for similar patterns in other test files for comparison
fd -t f '*.test.ts' -x rg -l 'it.skip|skipIf' {} \; | head -5

Repository: ThinkInAIXYZ/deepchat

Length of output: 2530


🏁 Script executed:

#!/bin/bash
# Check prcheck.yml to see unit test execution
cat .github/workflows/prcheck.yml | head -200

Repository: ThinkInAIXYZ/deepchat

Length of output: 4630


🏁 Script executed:

#!/bin/bash
# Verify if there's any explicit enforcement or CI-specific behavior for sqlite availability
rg -n 'CI|SKIP|sqlite' test/main/ocr/ocrArtifactStore.test.ts
# Check if there are other conditional test skips in the codebase for comparison
fd -t f '*.test.ts' -x rg -l 'it\.skip|skipIf' {} \; 2>/dev/null | head -5

Repository: ThinkInAIXYZ/deepchat

Length of output: 542


Enforce native SQLite availability in CI for encrypted persistence tests. The persistentIt tests (lines 110, 127) currently skip silently if better-sqlite3-multiple-ciphers fails to load, leaving encrypted persistence with zero coverage in the standard PR check workflow. The prcheck.yml does not run test:main, and there is no assertion that sqliteAvailable === true.

Apply the same environment-variable enforcement used in memory-native-validation (lines 52–55 of prcheck.yml) by either:

  • Adding DEEPCHAT_REQUIRE_NATIVE_SQLITE: '1' when running the OCR artifact store tests in CI, or
  • Asserting sqliteAvailable === true with a hard fail when the flag is set.

This mirrors the pattern already established for memory tests and ensures the encrypted-persistence path is actually exercised before merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/ocr/ocrArtifactStore.test.ts` around lines 15 - 26, Ensure
encrypted persistence tests cannot be silently skipped in required CI: update
the OCR artifact store test setup around sqliteAvailable and persistentIt to
honor DEEPCHAT_REQUIRE_NATIVE_SQLITE, failing when the flag is set but
better-sqlite3-multiple-ciphers is unavailable; also configure that environment
variable for the CI command running these tests if needed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
resources/model-db/providers.json (1)

236574-236580: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clamp Gemini 3.6 Flash output to 65536
resources/model-db/providers.json:236576-236578 advertises "output": 1048576, but this model’s published max output is 65,536 tokens. That value should be lowered to match the actual limit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/model-db/providers.json` around lines 236574 - 236580, Update the
Gemini 3.6 Flash model entry’s limit.output value from 1048576 to 65536, while
leaving its context and tool_call settings unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@resources/model-db/providers.json`:
- Around line 236574-236580: Update the Gemini 3.6 Flash model entry’s
limit.output value from 1048576 to 65536, while leaving its context and
tool_call settings unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19abf7c0-bc5f-4a74-8284-a134be85d1f8

📥 Commits

Reviewing files that changed from the base of the PR and between c626f06 and 104d718.

📒 Files selected for processing (15)
  • resources/acp-registry/registry.json
  • resources/model-db/providers.json
  • scripts/afterPack.js
  • scripts/smoke-light-ocr.js
  • src/main/ocr/lightOcrNativePayload.ts
  • src/main/ocr/lightOcrProcessHost.ts
  • src/main/ocr/ocrRuntimeAssetResolver.ts
  • src/main/ocr/ocrRuntimeService.ts
  • test/main/ocr/attachmentCapabilityRouter.test.ts
  • test/main/ocr/lightOcrNativePayload.test.ts
  • test/main/ocr/lightOcrProcessHost.test.ts
  • test/main/ocr/ocrRuntimeAssetResolver.test.ts
  • test/main/ocr/routes.test.ts
  • test/main/scripts/afterPack.test.ts
  • test/main/scripts/smokeLightOcr.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • test/main/ocr/routes.test.ts
  • test/main/ocr/ocrRuntimeAssetResolver.test.ts
  • src/main/ocr/ocrRuntimeService.ts
  • src/main/ocr/ocrRuntimeAssetResolver.ts
  • src/main/ocr/lightOcrProcessHost.ts
  • test/main/scripts/afterPack.test.ts
  • resources/acp-registry/registry.json
  • test/main/ocr/attachmentCapabilityRouter.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/main/scripts/lightOcrPackageSize.test.ts`:
- Around line 94-107: Extend the assertions in the test covering applicationStep
and feishuStep so both unsigned steps are verified not to contain CSC_LINK or
CSC_KEY_PASSWORD. Keep the existing signed cuaStep assertions unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83b44083-fcc9-4fdc-818c-9c660367df64

📥 Commits

Reviewing files that changed from the base of the PR and between 104d718 and b4ec8d3.

📒 Files selected for processing (2)
  • .github/actions/light-ocr-package-size/action.yml
  • test/main/scripts/lightOcrPackageSize.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/actions/light-ocr-package-size/action.yml

Comment on lines +94 to +107
expect(applicationStep).toBeDefined()
expect(applicationStep).not.toContain('CSC_LINK:')
expect(cuaStep).toBeDefined()
expect(cuaStep).toContain('CSC_LINK: ${{ inputs.csc-link }}')
expect(cuaStep).toContain('CSC_KEY_PASSWORD: ${{ inputs.csc-key-password }}')
expect(cuaStep).toContain(
"build_for_release: ${{ inputs.platform == 'darwin' && '2' || '' }}"
)
expect(cuaStep?.indexOf('build_for_release:')).toBeLessThan(
cuaStep?.indexOf('pnpm --dir .ocr-size-base run plugin:bundle') ?? -1
)
expect(feishuStep).toBeDefined()
expect(feishuStep).not.toContain('CSC_LINK:')
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Assert that both signing secrets are absent from unsigned steps.

The test only checks CSC_LINK; it can pass if CSC_KEY_PASSWORD is accidentally exposed to the baseline application or Feishu step. Assert both signing inputs are absent from both unsigned steps.

Proposed test adjustment
-    expect(applicationStep).not.toContain('CSC_LINK:')
+    expect(applicationStep).not.toContain('CSC_LINK:')
+    expect(applicationStep).not.toContain('CSC_KEY_PASSWORD:')
...
-    expect(feishuStep).not.toContain('CSC_LINK:')
+    expect(feishuStep).not.toContain('CSC_LINK:')
+    expect(feishuStep).not.toContain('CSC_KEY_PASSWORD:')
📝 Committable suggestion

‼️ 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.

Suggested change
expect(applicationStep).toBeDefined()
expect(applicationStep).not.toContain('CSC_LINK:')
expect(cuaStep).toBeDefined()
expect(cuaStep).toContain('CSC_LINK: ${{ inputs.csc-link }}')
expect(cuaStep).toContain('CSC_KEY_PASSWORD: ${{ inputs.csc-key-password }}')
expect(cuaStep).toContain(
"build_for_release: ${{ inputs.platform == 'darwin' && '2' || '' }}"
)
expect(cuaStep?.indexOf('build_for_release:')).toBeLessThan(
cuaStep?.indexOf('pnpm --dir .ocr-size-base run plugin:bundle') ?? -1
)
expect(feishuStep).toBeDefined()
expect(feishuStep).not.toContain('CSC_LINK:')
})
expect(applicationStep).toBeDefined()
expect(applicationStep).not.toContain('CSC_LINK:')
expect(applicationStep).not.toContain('CSC_KEY_PASSWORD:')
expect(cuaStep).toBeDefined()
expect(cuaStep).toContain('CSC_LINK: ${{ inputs.csc-link }}')
expect(cuaStep).toContain('CSC_KEY_PASSWORD: ${{ inputs.csc-key-password }}')
expect(cuaStep).toContain(
"build_for_release: ${{ inputs.platform == 'darwin' && '2' || '' }}"
)
expect(cuaStep?.indexOf('build_for_release:')).toBeLessThan(
cuaStep?.indexOf('pnpm --dir .ocr-size-base run plugin:bundle') ?? -1
)
expect(feishuStep).toBeDefined()
expect(feishuStep).not.toContain('CSC_LINK:')
expect(feishuStep).not.toContain('CSC_KEY_PASSWORD:')
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/scripts/lightOcrPackageSize.test.ts` around lines 94 - 107, Extend
the assertions in the test covering applicationStep and feishuStep so both
unsigned steps are verified not to contain CSC_LINK or CSC_KEY_PASSWORD. Keep
the existing signed cuaStep assertions unchanged.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
resources/model-db/providers.json (1)

260548-260614: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Fix the Gemma model types in resources/model-db/providers.json google/gemma-4-26b-a4b-it, google/gemma-4-26b-a4b-it:free, google/gemma-4-31b-it, and google/gemma-4-31b-it:free are text-only, so type: "imageGeneration" is incorrect here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/model-db/providers.json` around lines 260548 - 260614, Update the
provider entries for google/gemma-4-26b-a4b-it, google/gemma-4-26b-a4b-it:free,
google/gemma-4-31b-it, and google/gemma-4-31b-it:free to use the appropriate
text-model type instead of type: "imageGeneration"; preserve their existing text
output and capability metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@resources/model-db/providers.json`:
- Around line 260548-260614: Update the provider entries for
google/gemma-4-26b-a4b-it, google/gemma-4-26b-a4b-it:free,
google/gemma-4-31b-it, and google/gemma-4-31b-it:free to use the appropriate
text-model type instead of type: "imageGeneration"; preserve their existing text
output and capability metadata.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2f92920-45c6-4610-81cd-5e0feb466c71

📥 Commits

Reviewing files that changed from the base of the PR and between b4ec8d3 and 08f372f.

📒 Files selected for processing (10)
  • docs/features/light-ocr-integration/plan.md
  • docs/features/light-ocr-integration/spec.md
  • docs/features/light-ocr-integration/tasks.md
  • electron-builder.yml
  • resources/acp-registry/registry.json
  • resources/model-db/providers.json
  • scripts/apple-notarization.js
  • scripts/notarize-dmg.js
  • scripts/notarize.js
  • test/main/scripts/notarizeDmg.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/features/light-ocr-integration/spec.md
  • docs/features/light-ocr-integration/plan.md
  • resources/acp-registry/registry.json

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant