Skip to content

Commit eadc9f1

Browse files
committed
feat: improve settings, diagnostics and reader UX
Add an independent settings workspace, kernel-specific provider configuration and outcome history, native translation menus, system language support, and build information. Add developer quick tests, operation and resource metrics, recent debug logs with worker-based filtering, and scoped cache management with configurable limits. Improve document defaults and reopening behavior, sidebar window dragging, pager-styled zoom controls, and nonmodal kernel error dismissal with five-minute suppression. Validation: 114 Node tests passed; Vite production build and kernel error Electron smoke passed. Sidebar smoke remains blocked by its tab animation assertion.
1 parent 3f0023d commit eadc9f1

91 files changed

Lines changed: 6608 additions & 268 deletions

File tree

Some content is hidden

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

‎.github/workflows/electron-build.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ jobs:
4949
extra-arch: armv7l
5050
steps:
5151
- uses: actions/checkout@v5
52+
with:
53+
fetch-depth: 0
5254
- uses: actions/setup-node@v5
5355
id: node
5456
with:

‎README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Read scientific documents in any language, with realtime translation, on any pla
2020
- Navigate with thumbnails, zoom, fit-to-page controls, vertical or horizontal scrolling, and one-, two-, or four-page layouts.
2121
- Resume recent documents with their reading position and display settings restored.
2222
- Choose full-document or nearby-page translation, and click detected paragraphs to toggle original text and translation.
23-
- Configure translation language, concurrency, and kernel-specific options in Settings. Interface language is configured separately.
23+
- Configure translation language, concurrency, and kernel-specific options in Settings. On macOS, Settings opens in an independent window with native window controls and draggable sidebar/header areas. General and appearance settings use a two-column layout; Providers and Kernel use a category sidebar, a service/options list, and a detail pane. Provider catalogs follow the selected kernel. The previous settings interface remains available from **Previous settings**. Both interfaces share the same preferences and credential storage. Interface language is configured separately. The **Translation** menu lists the current kernel’s source/target languages, kernel choices, interface languages, and providers that are configured, system supplied, or automatic. Menu selections share the settings state and restore saved provider profiles.
2424
Target languages follow the selected kernel: Inspector offers 51 choices, Fast offers its 10 upstream languages, and Precise offers 47 choices based on BabelDOC’s [supported languages](https://funstory-ai.github.io/BabelDOC/supported_languages/). Language names follow all seven interface locales; source languages use the shared 51-language catalog. Switching kernels keeps compatible selections and resets unsupported targets to Simplified Chinese.
2525
Existing Advanced option labels, descriptions, and font-category choices follow the interface language in all seven supported languages. Newly added kernel options fall back to their upstream text until translations are provided; saved parameter values stay unchanged.
2626
- Create saved bidirectional links between a search result and its reading origin, with link buttons available in both original and translated views.
@@ -109,6 +109,8 @@ npm run package:win
109109

110110
Frontend libraries (Vue, MacVue, and Fluent UI) are build dependencies: Vite includes them in `dist`. Node dependencies used by the server or Electron main process remain runtime dependencies. Install with `npm ci` before building; `npm ci --omit=dev` cannot build or package the app.
111111

112+
The About page shows the application, installed kernels and UV versions. Each Vite build embeds `dist/build-info.json` with the package version and the latest ten conventional `feat` commits (including scoped and breaking features); release CI checks out the full Git history. The installed app reads this snapshot without Git or network access. Builds from a source archive without Git show an empty update list.
113+
112114
The default Electron package bundles Express and PDF utilities into the backend/main scripts, retaining their licenses. It copies only external runtime modules into the staged `node_modules`; native PDF Inspector bindings and the PDF.js/DOMMatrix fallback for unsupported native targets remain available. Electron itself and packaging tools are supplied by the build toolchain.
113115

114116
For browser development, set `OPENAI_API_KEY`, run `npm run dev`, and open [127.0.0.1:5173](http://127.0.0.1:5173). Use `OPENAI_MODEL` to override the default model. Desktop environment variables can be loaded with `Launch PDFMathReader.command`.

‎doc/developer-tests.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Developer quick tests
2+
3+
Open the developer console from Settings → Developer mode. The quick-test panel uses the selected reader window's settings at the moment a test starts: kernel, source and target languages, concurrency, advanced options, active translation provider, saved profiles, and securely stored credentials.
4+
5+
- **Test kernel communication** creates a disposable one-page sample PDF. Ultra fast extracts and groups its text; Fast and Precise run their real PDF workers with a local simulated translation response. This checks PDF/kernel communication without sending a request to an external translation provider.
6+
- **Test current provider** sends a small example using the active provider. Fast and Precise native providers are exercised through their real kernel. A test always requests a fresh response rather than accepting a translation cache hit.
7+
- **Batch test providers** tests the configured profiles for the selected kernel sequentially. Incomplete profiles appear as skipped. A failed provider does not stop the remaining providers.
8+
9+
Provider tests make real translation requests and may use the provider's quota. They translate only the generated sample, not the open document. Results include status, elapsed time, errors, and available sample output. Cancel stops the active request and pending batch items; closing the console also cancels the run. Provider selection and saved settings are not changed.
10+
11+
The developer bridge exposes provider labels and readiness, never credentials. Tests are opt-in, require an authorized developer window, and the backend refuses them while developer collection is disabled. Missing kernels are reported rather than automatically installed.
12+
13+
Validation:
14+
15+
```zsh
16+
npm run build
17+
node --test server/developer-test-runner.test.mjs server/developer-tests.test.mjs
18+
node_modules/.bin/electron electron/developer-quick-tests-smoke.mjs
19+
```
20+
21+
The standalone Electron smoke uses isolated settings, a temporary cache, and simulated provider responses; it does not contact a real translation provider.
22+
23+
## Recent debug logs
24+
25+
Advanced settings shows a Recent debug logs section when the selected kernel's Debug option or Developer mode is enabled. It lists the latest 200 redacted events for the selected kernel in gray, selectable text. Log collection for the Debug option works without opening the developer console.
26+
27+
The regex filter accepts a pattern such as `error|timeout` (case insensitive) or an explicit literal such as `/timeout/i`. Invalid or excessively slow expressions display a message and show all recent logs. Matching runs in a worker, which is stopped if it takes more than one second. Polling stops when the section is inactive or the window is hidden.

‎doc/translation-services.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# Custom translation services
22

3-
The Translation section selects a service independently for Inspector, Legacy and Next. The fields come from each installed Python kernel's translator definitions and CLI parser; updating or reinstalling that kernel invalidates its cached schema. Schema discovery is lazy and shared between concurrent requests, with an on-disk cache keyed by app and kernel version. The settings panel also caches loaded schemas.
3+
The Translation Services page selects a service independently for Inspector, Legacy and Next. The fields come from each installed Python kernel's translator definitions and CLI parser; updating or reinstalling that kernel invalidates its cached schema. Schema discovery is lazy and shared between concurrent requests, with an on-disk cache keyed by app and kernel version. The settings panel also caches loaded schemas.
44

55
Automatic retains the existing OpenAI key / SiliconFlow free fallback. Explicit Python services run using their own upstream service selector and environment parameters. Inspector offers an OpenAI compatible endpoint with API key, base URL and model. Each service remembers its own inputs. Desktop secret fields are encrypted with Electron safeStorage and stored separately from ordinary reader preferences.
66

7+
The provider list is divided into Configured, Not configured and Errors. Configuration checks use the kernel's required fields, saved non-secret values and separately encrypted credentials. Only recorded service failures appear under Errors; request validation, PDF processing, cancellation and cache reads do not mark a service as failed. A later real successful call clears the error status. History stores only the last outcome and timestamp per kernel and service, without error messages or credentials.
8+
79
On macOS 26 and later, Apple Translation is available without an API key or base URL. It translates on device using Apple's Translation framework. Both language models must already be installed. Missing models produce an actionable error and do not fall back to a cloud provider. Legacy and Next send raw text through an app-owned adapter; Next uses its text translation path and disables automatic LLM glossary extraction for this service. LLM prompts do not apply to Apple Translation.
810

911
Validation:

‎electron/about-smoke.mjs‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {app} from 'electron';
2+
import assert from 'node:assert/strict';
3+
import {writeFile,readFile} from 'node:fs/promises';
4+
export async function verifyAbout(reader,createWindow){
5+
const evaluate=(target,code)=>target.webContents.executeJavaScript(code);
6+
const wait=async(target,code)=>{for(let n=0;n<400;n++){if(await evaluate(target,code))return;await new Promise(r=>setTimeout(r,50));}throw Error('About timed out: '+code);};
7+
await wait(reader,'window.previewReady===true');
8+
const metadata=JSON.parse(await readFile(new URL('../dist/build-info.json',import.meta.url),'utf8'));
9+
const settings=await createWindow(null,null,reader,'about');settings.show();settings.focus();
10+
await wait(settings,`document.querySelector('[data-version=app]')?.textContent===${JSON.stringify(metadata.version)}&&/\\d+\\.\\d+/.test(document.querySelector('[data-version=pdf_math_precise]')?.textContent||'')&&/\\d+\\.\\d+/.test(document.querySelector('[data-version=uv]')?.textContent||'')`);
11+
const result=await evaluate(settings,`(()=>{const section=document.querySelector('.about-versions');return {first:section.closest('.workspace-page').firstElementChild===section,versions:[...section.querySelectorAll('[data-version]')].map(el=>({id:el.dataset.version,text:el.textContent})),features:[...section.querySelectorAll('.feature-updates li span')].map(el=>el.textContent)}})()`);
12+
assert.equal(result.first,true);assert.equal(result.versions.length,5);
13+
assert.equal(await evaluate(settings,`[...document.querySelectorAll('.settings h3,.settings .setting-row span,.settings .effective-translation-summary,.settings .dependency-project span')].every(el=>getComputedStyle(el).userSelect==='none')`),true);
14+
const heading=await evaluate(settings,`(()=>{window.getSelection().removeAllRanges();const r=document.querySelector('#settings-about-versions').getBoundingClientRect();return {x:Math.round(r.x+4),y:Math.round(r.y+r.height/2),end:Math.round(r.x+140)}})()`);
15+
settings.webContents.sendInputEvent({type:'mouseDown',x:heading.x,y:heading.y,button:'left',clickCount:1});
16+
settings.webContents.sendInputEvent({type:'mouseMove',x:heading.end,y:heading.y,button:'left'});
17+
settings.webContents.sendInputEvent({type:'mouseUp',x:heading.end,y:heading.y,button:'left',clickCount:1});
18+
assert.equal(await evaluate(settings,`window.getSelection().toString()`),'');
19+
assert.equal(await evaluate(settings,`(()=>{const input=document.querySelector('.sidebar-search input');input.value='editable';input.focus();input.setSelectionRange(0,8);const works=getComputedStyle(input).userSelect==='text'&&input.selectionEnd-input.selectionStart===8;input.value='';input.blur();return works})()`),true);
20+
21+
for(const version of result.versions)assert.match(version.text,/\d+\.\d+/);
22+
assert.deepEqual(result.features,metadata.recentFeatures.map(item=>item.subject));assert.ok(result.features.length<=10);
23+
await evaluate(reader,`window.previewPreferences.save({uiLanguage:'zh-CN'})`);
24+
await wait(settings,`document.querySelector('#settings-about-versions')?.textContent==='版本与更新'`);
25+
await writeFile('/tmp/pdfmathreader-about-versions.png',(await settings.webContents.capturePage()).toPNG());
26+
settings.setSize(680,780);await new Promise(r=>setTimeout(r,150));
27+
assert.equal(await evaluate(settings,`(()=>{const page=document.querySelector('.about-versions').closest('.workspace-page');return page.scrollWidth<=page.clientWidth+1})()`),true);
28+
console.log('About passed: first section, real app/three kernels/UV versions, build snapshot feature list and narrow layout.',JSON.stringify(result));
29+
app.quit();
30+
}

0 commit comments

Comments
 (0)