@qilin-agent/client-ui-sidebar-documentpreview 0.0.0-stage → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +46 -0
- package/README.md +158 -2
- package/README.zh.md +159 -0
- package/lib/client.excel.js +133808 -0
- package/lib/client.js +7758 -0
- package/lib/client.pdf.js +32672 -0
- package/lib/index.js +35 -0
- package/lib/types/client/LoadingIndicator.d.ts +12 -0
- package/lib/types/client/TextPreview.d.ts +29 -0
- package/lib/types/client/TextTitle.d.ts +14 -0
- package/lib/types/client/code/CodeBody.d.ts +9 -0
- package/lib/types/client/code/index.d.ts +5 -0
- package/lib/types/client/code/languages.d.ts +9 -0
- package/lib/types/client/code/locales.d.ts +19 -0
- package/lib/types/client/definition.d.ts +37 -0
- package/lib/types/client/document/contract.d.ts +111 -0
- package/lib/types/client/document/registry.d.ts +70 -0
- package/lib/types/client/document/resource-group.d.ts +23 -0
- package/lib/types/client/document/suffix.d.ts +24 -0
- package/lib/types/client/document/tab-lifetime.d.ts +10 -0
- package/lib/types/client/document/unviewable.d.ts +7 -0
- package/lib/types/client/excel/LazyExcelBody.d.ts +22 -0
- package/lib/types/client/excel/convert.d.ts +12 -0
- package/lib/types/client/excel/delimited.d.ts +10 -0
- package/lib/types/client/excel/error.d.ts +7 -0
- package/lib/types/client/excel/excel.d.ts +11 -0
- package/lib/types/client/excel/format.d.ts +9 -0
- package/lib/types/client/excel/index.d.ts +10 -0
- package/lib/types/client/excel/locales.d.ts +45 -0
- package/lib/types/client/excel/model.d.ts +35 -0
- package/lib/types/client/excel/parse.d.ts +12 -0
- package/lib/types/client/excel/worker.d.ts +2 -0
- package/lib/types/client/excel/xls.d.ts +18 -0
- package/lib/types/client/excel/xlsx-archive.d.ts +21 -0
- package/lib/types/client/excel/xlsx.d.ts +9 -0
- package/lib/types/client/face.d.ts +87 -0
- package/lib/types/client/failure-line.d.ts +17 -0
- package/lib/types/client/html/HtmlBody.d.ts +22 -0
- package/lib/types/client/html/basic-document.d.ts +7 -0
- package/lib/types/client/html/bootstrap.d.ts +20 -0
- package/lib/types/client/html/bytes.d.ts +14 -0
- package/lib/types/client/html/index.d.ts +17 -0
- package/lib/types/client/html/locales.d.ts +23 -0
- package/lib/types/client/html/pack.d.ts +21 -0
- package/lib/types/client/html/read-relative.d.ts +22 -0
- package/lib/types/client/icons.d.ts +14 -0
- package/lib/types/client/image/ImageBody.d.ts +32 -0
- package/lib/types/client/image/index.d.ts +21 -0
- package/lib/types/client/image/locales.d.ts +37 -0
- package/lib/types/client/index.d.ts +53 -0
- package/lib/types/client/locales.d.ts +66 -0
- package/lib/types/client/markdown/MarkdownBody.d.ts +12 -0
- package/lib/types/client/markdown/index.d.ts +17 -0
- package/lib/types/client/markdown/locales.d.ts +27 -0
- package/lib/types/client/markdown/path-images.d.ts +9 -0
- package/lib/types/client/markdown/sanitize-html.d.ts +34 -0
- package/lib/types/client/media-url.d.ts +15 -0
- package/lib/types/client/office/FontNotice.d.ts +16 -0
- package/lib/types/client/office/OfficeBody.d.ts +44 -0
- package/lib/types/client/office/cache.d.ts +70 -0
- package/lib/types/client/office/index.d.ts +16 -0
- package/lib/types/client/office/locales.d.ts +43 -0
- package/lib/types/client/office/store.d.ts +32 -0
- package/lib/types/client/pdf/LazyPdfBody.d.ts +10 -0
- package/lib/types/client/pdf/assets.d.ts +26 -0
- package/lib/types/client/pdf/document.d.ts +38 -0
- package/lib/types/client/pdf/errors.d.ts +8 -0
- package/lib/types/client/pdf/index.d.ts +27 -0
- package/lib/types/client/pdf/locales.d.ts +45 -0
- package/lib/types/client/pdf/pdf.d.ts +29 -0
- package/lib/types/client/pdf/runtime.d.ts +14 -0
- package/lib/types/client/pdf/store.d.ts +27 -0
- package/lib/types/client/pdf/text.d.ts +10 -0
- package/lib/types/client/rpc.d.ts +77 -0
- package/lib/types/client/selection-payload.d.ts +53 -0
- package/lib/types/client/selection-popup.d.ts +58 -0
- package/lib/types/client/store.d.ts +100 -0
- package/lib/types/client/text/TextBody.d.ts +6 -0
- package/lib/types/client/text/index.d.ts +14 -0
- package/lib/types/client/text/lines.d.ts +31 -0
- package/lib/types/client/video/VideoBody.d.ts +19 -0
- package/lib/types/client/video/index.d.ts +15 -0
- package/lib/types/client/video/locales.d.ts +21 -0
- package/lib/types/client/zoom/ZoomControls.d.ts +21 -0
- package/lib/types/client/zoom/ZoomViewport.d.ts +27 -0
- package/lib/types/client/zoom/locales.d.ts +21 -0
- package/lib/types/client/zoom/store.d.ts +28 -0
- package/lib/types/client/zoom/types.d.ts +27 -0
- package/lib/types/config.d.ts +31 -0
- package/lib/types/index.d.ts +11 -0
- package/package.json +100 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kkutysllb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/client/ui-sidebar-documentpreview/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: 61723a1508d644a3
|
|
7
|
+
zh: 634fa3444e04ceed
|
|
8
|
+
/qilin-agent-client-ui-sidebar-documentpreview:
|
|
9
|
+
en: 00d3b5e7dc51623a
|
|
10
|
+
zh: 36068a59e373166a
|
|
11
|
+
/qilin-agent-client-ui-sidebar-documentpreview/summary:
|
|
12
|
+
en: 696aafd0c39c6469
|
|
13
|
+
zh: b29ae74fa99ff2f2
|
|
14
|
+
/qilin-agent-client-ui-sidebar-documentpreview/table-of-contents:
|
|
15
|
+
en: 0dd8c3a22e28aacf
|
|
16
|
+
zh: c27c11bf4b5cceba
|
|
17
|
+
/qilin-agent-client-ui-sidebar-documentpreview/what-it-registers:
|
|
18
|
+
en: ec53db16190e0953
|
|
19
|
+
zh: a6aab2117fab9f12
|
|
20
|
+
/qilin-agent-client-ui-sidebar-documentpreview/addresses:
|
|
21
|
+
en: 3b4b340123bf608e
|
|
22
|
+
zh: ef5d2422a2483eff
|
|
23
|
+
/qilin-agent-client-ui-sidebar-documentpreview/how-it-reads:
|
|
24
|
+
en: cf48bf827db7a9b1
|
|
25
|
+
zh: b492d2f91835961e
|
|
26
|
+
/qilin-agent-client-ui-sidebar-documentpreview/excel-preview:
|
|
27
|
+
en: 9588eca681e6d323
|
|
28
|
+
zh: 4f990e58ed5272d7
|
|
29
|
+
/qilin-agent-client-ui-sidebar-documentpreview/office-preview:
|
|
30
|
+
en: 3ef3045432b4f93e
|
|
31
|
+
zh: 533a91496b0f1f91
|
|
32
|
+
/qilin-agent-client-ui-sidebar-documentpreview/navigation:
|
|
33
|
+
en: 5cd81a7b3cdc83bd
|
|
34
|
+
zh: 4cdd1b7ede497925
|
|
35
|
+
/qilin-agent-client-ui-sidebar-documentpreview/model-experience:
|
|
36
|
+
en: 95f576877100b1b2
|
|
37
|
+
zh: 7c9164ec6865d709
|
|
38
|
+
/qilin-agent-client-ui-sidebar-documentpreview/model-experience/kv-cache-effect:
|
|
39
|
+
en: 1affa1bb1b923da5
|
|
40
|
+
zh: 88a0d4200068ab57
|
|
41
|
+
/qilin-agent-client-ui-sidebar-documentpreview/known-limitations-and-deferred-work:
|
|
42
|
+
en: d3854fdfa2c08763
|
|
43
|
+
zh: c18198585d4dc0cd
|
|
44
|
+
/qilin-agent-client-ui-sidebar-documentpreview/known-limitations-and-deferred-work/dev-note:
|
|
45
|
+
en: e67f633dc6fe7773
|
|
46
|
+
zh: e88aeb617ffb74ea
|
package/README.md
CHANGED
|
@@ -1,3 +1,159 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Document previews in the right Sidebar: shared file loading and controls, selectable Markdown, code, image, video, PDF, spreadsheet, Office and HTML renderers, and plain-text fallback."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @qilin-agent/client-ui-sidebar-documentpreview
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Preview readable files in the right Sidebar and choose among registered renderers without opening another tab. Markdown and code receive accumulated text pages; PDF, HTML, common images, and spreadsheets receive complete bytes; unknown file extensions use plain text. Word and PowerPoint documents convert locally to PDF; spreadsheets open in the browser. Video streams from the host media route over HTTP Range, so scrubbing skips the file reader. The tab owns loading, file status, renderer selection, wrap, and automatic or manual reload, while document bodies register through the same metadata registry and child slot. The Sidebar tab kind is `text`.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [What it registers](#what-it-registers)
|
|
17
|
+
- [Addresses](#addresses)
|
|
18
|
+
- [How it reads](#how-it-reads)
|
|
19
|
+
- [Excel preview](#excel-preview)
|
|
20
|
+
- [Office preview](#office-preview)
|
|
21
|
+
- [Navigation](#navigation)
|
|
22
|
+
- [Model Experience](#model-experience)
|
|
23
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
24
|
+
- [Dev Note](#dev-note)
|
|
25
|
+
|
|
26
|
+
-----
|
|
27
|
+
|
|
28
|
+
<a id="what-it-registers"></a>
|
|
29
|
+
## What it registers
|
|
30
|
+
|
|
31
|
+
- **The type** — `ctx.sidebarRightTabs.register(...)` with id `@qilin-agent/client-ui-sidebar-documentpreview` (this implementation's identity in the tab system, and the key its body registers under), kind `text`, pattern `qilin-resource://file/**`, band `fallback`. `canOpen` accepts only Session addresses, whose paths may be relative or absolute; bare `absolute` addresses are not claimed. A type registered at the `extension` or `builtin` band for a narrower pattern (say `*.png`) takes those addresses; other supported files land here. The whole address is the content identity, so two files with one name in different directories, or one path under two sessions, are two tabs; the decoded basename is the tab title, and the keyed `sidebar.right.pane.tab.title` seat places its extension-specific `FileTypeIcon` before that title.
|
|
32
|
+
- **The body** — the keyed `sidebar.right.pane.tab` seat under the type's id. Its fixed header shows the Host's absolute path when available, otherwise the requested path; directories use tertiary label colour, the name uses primary label colour, and a clipped path retains and fades toward its final segment while its tooltip exposes the full value. A dropdown appears when multiple supported renderers are available. Plain text is offered only for text-compatible sources; a single renderer shows no viewer control. A known binary container suffix without a registered renderer shows the file-type icon and an unsupported-preview message under the path header, without issuing a read. A wrap toggle appears only when the selected renderer declares `wrap: true`; its glyph describes the mode the click selects, and the per-tab preference starts on. Reload stays in this header, not the Sidebar's tab strip. Automatic refresh defaults to enabled; its separate toggle is hidden, with state, labels, styles, and toggle logic retained. The body reaches every pane edge; each renderer owns its content inset and may own an inner scrollport. This intentionally differs from the Files tab's 2px right-side scrollbar offset: previews keep the full pane width so edge-to-edge HTML and code scrollports end at the pane edge. A selection in a text body anchors an **add to conversation** button above it; the click appends a fenced block whose info line is the viewer's path with the selected line span, to the composer draft of the tab's own Session, as one undo step that leaves the draft's existing text and reference chips in place. The bodies that hold bytes rather than the file's own text — PDF, image, Office, HTML, video — offer no selection action, because the rendered selection cannot be reverse-searched for source lines.
|
|
33
|
+
- **The file seats** — three child slots of that body registration hand the previewed file to other plugins. The list seats `sidebar.right.tab.document.actions`, rendered at the end of the path row, and `sidebar.right.tab.document.unpreviewable`, rendered under the unsupported-preview notice, receive `{ absolutePath }` and are withheld until the Host reports the file's absolute path, so a contribution that hands the file to the Host never invents one. The keyed `sidebar.right.tab.document.action` seat places renderer-specific controls before the reload button and receives the current `content` plus the enclosing `useTabInfo` hook context. The file opener in `qilin-client-ui-open-in-app` occupies both list seats.
|
|
34
|
+
- **Shared loading and view state**, session-scoped and bucketed by tab id. The store holds accumulated pages or complete bytes, read and observed versions, pending Resource changes, automatic-refresh state, loading/failure state, renderer choice, scroll offset, wrap, and the answered navigation revision. The ordinary inject face calls Remote readers and writes through declared store actions. Reloads and loading-mode changes retire older requests; the tab's abort signal forgets its state.
|
|
35
|
+
|
|
36
|
+
Document implementations register metadata with `ctx.documentPreviews.register({ id, extensions, binaryExtensions?, priority, title, loading, wrap? })` and a body under the same `id` in the keyed, Session-scoped `sidebar.right.tab.document` child slot. `binaryExtensions` lists suffixes in `extensions` that cannot be read as text and omit the plain-text option. Own both registrations with effects and wait for the child slot through `ctx.slots.inject`. Bodies receive `resourceAddress`, `content`, `wrap`, `scrollportRef`, `addResource`, `setResources`, and the standard `useTabInfo`/`useResource` hooks. Dependency membership belongs to a tab-owned `ResourceGroup`; replacing dependencies always retains the root Resource. An inner scrolling element attaches `scrollportRef`; unmounting restores the shared body as scroll owner. The registry retains all matching alternatives: `extension` (the default) ranks above `builtin`, then longer suffixes rank first, then registration order. The dropdown preserves a selected implementation while it remains available. HTML, SVG, and unmatched extensions retain the plain-text fallback independently of loading mode.
|
|
37
|
+
|
|
38
|
+
`loading: 'text-pages'` and `'bytes-complete'` use the shared file reader. With `'renderer'`, the selected body mounts before any bytes are read and receives `content: { kind: 'renderer', revision, loaded, failed, reload }`. Its injected callbacks own content loading, errors, and cancellation. `loaded(version)` records the displayed source version and ends loading. `failed()` ends loading without recording a successful version, allowing a later file change to retry; reports from replaced revisions are ignored. `reload()` increments the revision, which the body observes to cancel and replace its request. The body also cancels on unmount and tab closure, retains settled content in its declared tab store, and releases that state when the tab ends. [Office previews](#office-preview) use this mode without putting converted bytes or font metadata in the shared file store.
|
|
39
|
+
|
|
40
|
+
<a id="addresses"></a>
|
|
41
|
+
## Addresses
|
|
42
|
+
|
|
43
|
+
A tab uses the Session address built by `fileAddressFor`, carrying a relative or absolute path. `hostFileOf(address)` takes the Session only from that address, with no external Session argument; neither current nor Tab Session is borrowed. The Host resolves file and related paths through the Session filesystem, whose backend controls read authority. Metadata for the same complete address is shared by every UI, including Global components. The [Workspace Files README](../../api/workspace-files/README.md) owns these rules; renderer selection does not change the navigation address.
|
|
44
|
+
|
|
45
|
+
After all text pages load, Markdown images use the authenticated `/api/file` route for absolute file paths and paths relative to the source document's directory. Relative images wait for the Host's absolute document path. URL escapes are decoded once; query and fragment suffixes are excluded from the filename. HTTP(S) images retain their authored URLs, and failed image loads show alt text. Local images require an HTTP(S) application base URL; image files are not added to automatic-refresh dependencies.
|
|
46
|
+
|
|
47
|
+
<a id="how-it-reads"></a>
|
|
48
|
+
## How it reads
|
|
49
|
+
|
|
50
|
+
Coding Tools selects the HTML preview policy. The renderer receives `interactivePreview` through its registrant's inject hooks, bound to the shared Coding Tools preference. Off uses a DOMPurify-sanitized complete static document in an iframe with no sandbox permissions: CSP blocks scripts, external resources, connections, forms, and nested frames; all `href` and `xlink:href` attributes, refresh directives, and declarative shadow roots are removed before reparsing. Inline styles and data images remain visible, and related files are not read. On uses the scripted Blob preview described below. A mode change unmounts the previous frame and aborts its pending related-file reads. Static preview releases CSS/JS resource subscriptions and keeps the root file watched. Other document formats retain their own policies.
|
|
51
|
+
|
|
52
|
+
Both HTML modes set the iframe name to `qilin-sidebar-html-<tab-id>`, giving the frame a stable identity tied to its tab; the name does not grant the preview access to the parent document.
|
|
53
|
+
|
|
54
|
+
The body reads its record, navigation and lifetime through `useTabInfo().tab`. `useResource<'file'>(tab.contentId)` supplies metadata; ordinary inject callbacks supply content reads:
|
|
55
|
+
|
|
56
|
+
- The resource snapshot contains only `status`, `value`, and `failure`; `value` is `WorkspaceFileStat` metadata. Content reads do not wait for the first metadata frame once the provider is available. Observation failures take precedence over Preview's change notice; loaded content remains while metadata is unavailable.
|
|
57
|
+
- **Text pages** — plain text, Markdown, and code read through an inject callback to `remote.workspaceFiles.read(sessionId, path, { offset }, signal)`. The first mount reads page one; scrolling to the body end or **Load more** requests the next page until `eof`. The owner delivers the accumulated prefix as `{ kind: 'text', text, pages, eof }`, including source offsets and line counts. Markdown and code render that prefix incrementally; they do not render each page as a separate document. A newer-version page past page one restarts from the beginning rather than mixing versions. A failure before any content fills the body with the file-type icon, explanation, and retry; a later failure retains loaded content and adds the retry below it.
|
|
58
|
+
- **Complete bytes** — PDF, HTML, and common images use an inject callback to `remote.workspaceFiles.readAll(sessionId, path, signal)`. `rpc.ts` decodes the wire base64 into `data: Uint8Array<ArrayBuffer>` for `{ kind: 'bytes', data }`. The Host's `maxFileBytes` cap rejects oversized files rather than truncating them. PDF copies retained bytes before worker transfer, keeping the Preview buffer usable. Bytes stay in transient view state, never persisted layouts or Session JSONL. Loading-mode changes retire previous results.
|
|
59
|
+
- **Reload** — manual reload rereads only the current Preview tab through its Remote callbacks, preserving its scroll preference and retiring older requests. Automatic refresh uses the same callbacks after a `ResourceGroup` member changes. Each member's first metadata establishes a baseline without triggering reload or first-read version reconciliation; later changes received during a read remain pending for another refresh. Reads neither refresh shared metadata nor clear another tab's notice.
|
|
60
|
+
|
|
61
|
+
When Coding Tools is enabled, HTML fills the body edge to edge in a Blob iframe with exactly `sandbox="allow-scripts"`, without `allow-same-origin`; scripts cannot access the parent application's origin or file reader. The renderer loads directly declared relative `.js` classic scripts and `.css` stylesheets through its ordinary inject callback to `remote.workspaceFiles.readRelated`, with fixed safety limits of 4 MiB per asset, 32 MiB total, and 64 distinct assets. Host code resolves the related path; `rpc.ts` decodes the returned bytes. The dependency Resource is added after `readRelated` returns, using its `absolutePath` or a string `error.details.path` on failure; without a Host path, the Client does not guess one. Inside the renderer, base64 is used only to embed the iframe bootstrap payload in script text. A `<base href>` leaves dependency resolution to the browser, as do HTTPS resources. Local module imports, CSS `url()`/`@import`, and dynamic `fetch` do not use Host file access. Read failures, invalid UTF-8, or exceeded limits fail the preview rather than publishing a partial asset package. Replacing or unmounting the document releases its Blob URL.
|
|
62
|
+
|
|
63
|
+
PNG, JPEG, GIF, WebP, BMP, ICO, and SVG render through Blob URLs in an `<img>` static-image context, rounded inside a 12px inset. Images default to fit width without enlarging content smaller than the pane; 100% uses the image's intrinsic CSS-pixel width. The shared zoom controls can produce horizontal and vertical scrolling, but do not provide drag-to-pan. Zoom changes retain the same `<img>` and Blob URL, so animated images continue playing. Raster formats can soften above their intrinsic size, while SVG remains on the browser's vector rendering path. SVG markup never enters the application DOM or an iframe, so its scripts cannot execute or reach the parent page. Replacing or unmounting the image revokes its Blob URL.
|
|
64
|
+
|
|
65
|
+
Video extensions (`mp4`, `webm`, `mov`, `mkv`, and the rest of the claimed list) stream through a `<video>` element whose source is the authenticated `/sidebar/media` route for the session file; HTTP Range windows answer scrubbing seeks, so playback never depends on the whole-file reader or its byte cap. TypeScript's `.ts` stays unclaimed because it collides with the source extension, and `m2ts` covers MPEG-TS. The renderer declares no wrap toggle and owns its localized labels under `sidebarVideo`.
|
|
66
|
+
|
|
67
|
+
Shared copy comes from `sidebarDocumentPreview`; each builtin renderer owns its localized labels. PDF and converted Office previews use a graphite background in light mode and a matte-black background in dark mode, with subtle page shadows and original document colors. PDF and image previews share a floating control with **Fit width**, 25%, 50%, 100%, 150%, and 200% choices plus 25% incremental zoom across the 25%–400% fixed range. Fit width is the default and tracks pane resizing; 100% means the PDF's 96-DPI page size or the image's intrinsic CSS-pixel size. On fine pointers, the control starts hidden, slides up when the pointer enters the bottom 72 CSS pixels, and slides down after a 420 ms leave delay; hover, keyboard focus, an open menu, and an active gesture keep it visible, while devices without hover keep it visible continuously. Chromium maps a macOS trackpad pinch to the same live, pointer-anchored path as Ctrl+wheel; the control displays the gesture percentage, and the settled value survives body remounts until the tab closes. HTML previews do not expose zoom controls. After zoom settles or fit width changes, nearby PDF pages redraw at the resolved scale multiplied by the device pixel ratio. The previous bitmap and selectable text stay visible until the replacement bitmap is ready; offscreen pages refresh when they approach the viewport.
|
|
68
|
+
|
|
69
|
+
Initial reads, additional pages, and HTML/PDF/image preparation share an icon-only loading spinner that exposes its label to assistive technology and respects reduced-motion preferences; every wait before content exists centres the spinner in the pane, so opening a file shows one spinner in one position until the body appears. Loaded pages stay visible while another page loads. The PDF body loads its package-local `client.pdf.js` chunk only when a PDF preview mounts; PDF.js, its Worker source, and embedded support data stay out of the startup `client.js`. PDF pages fill the pane's width edge to edge as one vertical sequence and render lazily near the viewport; an unrendered page holds its place as a quiet 3:4 placeholder block. PDF.js’s official TextLayerBuilder manages selection boundaries and normalized copying over an aligned text layer. Its companion styles keep blank line breaks unhighlighted; alignment accounts for PDF page units, page rotation, and viewport resizing, and page disposal cancels both layers. Image-only PDFs contain no selectable text. Code previews show source line numbers by default without including them in copied text; plain text uses the same font size and line height as code. Code sits on the pane's own background rather than the chat card's fill; its banner is adjacent to a full-height inner scrollport, so both scrollbars begin below the copy control.
|
|
70
|
+
|
|
71
|
+
<a id="excel-preview"></a>
|
|
72
|
+
## Excel preview
|
|
73
|
+
|
|
74
|
+
Open `.xlsx`, `.xls`, `.csv`, and `.tsv` directly in the browser with worksheet tabs, cell selection, copying, and a read-only formula bar. XLSX retains fonts, solid fills, borders, number formats, rich text, merged cells, row and column sizes, hidden rows/columns/sheets, and frozen headings. XLS retains saved values, formulas, number formats, merges, and available row/column metadata; fonts, borders, and frozen panes are unsupported. Workbooks display saved formula results without recalculating; missing results remain blank, and a compact formula-bar warning marks workbooks whose displayed results may be incomplete or inaccurate. The workbook viewport fills the preview pane and follows its size changes without reloading its sheets or selection. Worksheet tabs start at the left edge; overflowing tabs scroll with horizontal trackpad gestures or left/right navigation beside a separate zoom control. Spreadsheet controls retain dark text on their light background in both app themes. Pixel-based trackpad gestures pan the grid on both axes together, including diagonally, at the gesture's speed; frozen headings stay fixed. Reversing direction at a sheet edge retains the next gesture's movement. Selection statistics and inactive worksheet menu arrows are hidden. Frozen headings remain fixed while scrolling, without freeze dividers or drag handles. Spreadsheet preview does not call the Office conversion service.
|
|
75
|
+
|
|
76
|
+
XLSX preview omits DrawingML parts from an in-memory copy before parsing and ignores worksheet drawing references. The original file and worksheet XML remain unchanged. A notice above the table lists detected charts, images, shapes, and conditional formatting that are not displayed, and recommends opening the workbook in a system application. Files without these detected features have no notice; formula warnings remain separate.
|
|
77
|
+
|
|
78
|
+
CSV and TSV default to the spreadsheet viewer and also offer Plain text; CSV additionally offers syntax-highlighted Code. Commas and tabs delimit their fields respectively; quoted separators, escaped quotes, multiline fields, empty fields, and unequal row lengths are supported. The first row remains data. Values remain literal strings, including leading zeros, dates, booleans, and formula-looking text. Text files accept UTF-8 or BOM-marked UTF-16; invalid encoding receives conversion guidance. Malformed quoted fields fail the table preview rather than silently dropping data.
|
|
79
|
+
|
|
80
|
+
Configure `excel` on the same `ui-sidebar-documentpreview` entry. These limits complement the Host's complete-file read limit; they do not cap browser process memory or decompression allocations.
|
|
81
|
+
|
|
82
|
+
| Field | Default | Meaning |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `excel.maxBytes` | `16777216` (16 MiB) | Maximum source file bytes |
|
|
85
|
+
| `excel.maxCells` | `250000` | Maximum combined rectangular worksheet area, including empty cells |
|
|
86
|
+
| `excel.timeoutMs` | `15000` | Maximum parser Worker lifetime in milliseconds |
|
|
87
|
+
|
|
88
|
+
The lazy Excel chunk bundles FortuneSheet, ExcelJS for XLSX, SheetJS CE for XLS, and PapaParse for CSV/TSV. Package-local, React-independent adapters map parser output directly to FortuneSheet cells and share cell formatting and initial selection. Third-party license texts remain in the published chunk; SheetJS CE retains its Apache-2.0 terms. Each parse owns a disposable Worker and transfers a copy of retained file bytes; replacement, unmount, failure, and timeout terminate that Worker. The stylesheet is scoped to the Excel preview. Charts, drawings/images, pivot tables, conditional formatting, editing, recalculation, and export are unsupported; font availability, Excel column-width approximation, and theme-tint approximation can affect fidelity. Hyperlinks display as text without loading their targets. ExcelJS decodes entity spellings in cached XLSX string formula results again; a saved literal `<` is displayed as `<`.
|
|
89
|
+
|
|
90
|
+
The read-only formula bar displays formulas and cell text literally on one line, with horizontal scrolling for long content. Copying preserves an HTML table with escaped cell contents, including saved formula results. Retain these behaviors and worksheet selection when upgrading FortuneSheet.
|
|
91
|
+
|
|
92
|
+
The pinned [ExcelJS patch](../../../patches/exceljs@4.4.0.patch) resolves the workbook, styles, shared strings, worksheets, comments, Tables, and VML through package relationships, including absolute and relative targets and ASCII case-equivalent part names, and recognizes SpreadsheetML and VML names by namespace URI. Strict OOXML SpreadsheetML and relationship URIs map to the same supported preview features; this is not full Strict conformance. XML parts accept UTF-8 and either byte order of UTF-16; CDATA contributes literal text. Drawing and conditional-format notices follow relationships regardless of part directories. Unreferenced `xl/drawings/*.xml` parts and their relationship files are also omitted, without adding notices; comment VML remains. Missing referenced parts and ambiguous case-equivalent ZIP entries fail the preview. Comments and Table metadata survive parsing but have no dedicated preview controls. The patch covers the Node sources and `dist/exceljs.js`; its browser entry selects that patched bundle. Dependency upgrades must preserve both entry paths and pass the independent-writer regressions in `tests/excel-opc.client.spec.ts` and `tests/excel-xml.client.spec.ts`.
|
|
93
|
+
|
|
94
|
+
<a id="office-preview"></a>
|
|
95
|
+
## Office preview
|
|
96
|
+
|
|
97
|
+
Open `.doc`, `.docx`, `.ppt`, and `.pptx` as PDF previews with the same loading state, zoom controls, cancellation, and selectable text as PDF files. The [Host provider](../../document/office-to-pdf/README.md) performs local conversion and keeps its spreadsheet conversion API for other consumers; invalid files, conversion failures, and timeouts receive localized messages. Missing Host services show configuration guidance.
|
|
98
|
+
|
|
99
|
+
The [Web bundle](../../bundle/web-app/README.md) mounts this package as `ui-sidebar-documentpreview`. Configure its transient Office cache through that entry's `office` settings; the [configuration catalog](../../../docs/config-catalog.md#qilin-agentclient-ui-sidebar-documentpreview) defines accepted values. Settings are embedded in each served page; reload the browser page after changing YAML.
|
|
100
|
+
|
|
101
|
+
| Field | Default | Meaning |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `office.maxCachedEntries` | `8` | Maximum retained completed PDFs |
|
|
104
|
+
| `office.maxCachedBytes` | `67108864` (64 MiB) | Maximum retained binary PDF bytes, counted by buffer `byteLength` |
|
|
105
|
+
| `office.maxPending` / `office.maxReaders` | `8` / `32` | Unsettled conversion RPCs / readers including metadata lookups |
|
|
106
|
+
|
|
107
|
+
Opening an Office file requests conversion on demand. Each read checks renderer generation and authorized source metadata before sharing an in-flight conversion or reusing a successful PDF. The cache uses renderer generation, Session, absolute source path, and source version as its identity; least-recently used PDFs leave to keep retention within both limits. Failures and PDFs larger than the byte limit are not retained. A converted PDF is retained only when its returned renderer generation matches the cache identity. Cancelling one reader leaves shared conversion running until the final reader leaves. Connection resets clear cached bytes and cancel pending reads; plugin disposal also waits for outstanding requests. PDFs are never persisted; transport strings, decoding storage, and PDF.js rendering memory remain outside the cache limit.
|
|
108
|
+
|
|
109
|
+
Background requests leave the final pending-request and reader slots available for foreground work; setting either limit to one rejects background reads. Renderer replacement restarts generation discovery and authorization once. Another replacement during that retry reports the localized busy message.
|
|
110
|
+
|
|
111
|
+
A yellow notice identifies fonts unavailable during conversion. Show more opens an anchored list; closing the list preserves the notice. Dismissing the notice collapses its space and moves the PDF upward. Dismissal applies to the same source version while the preview remains mounted; switching away from the tab and back shows the notice again. Long notice text fades before the action, and reduced-motion preferences disable the collapse animation.
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary>Office implementation — click to expand</summary>
|
|
115
|
+
|
|
116
|
+
Office registration, loading, caching, and font notices live in `src/client/office/`. The Office body owns converted PDF bytes and font metadata, and declares a nested PDF slot that reuses the lazy PDF body and its tab viewing state. The notice sits above the Office scrollport. Registration remains available without the Host renderer; optional `remote.officeToPdf` and `remote.workspaceFiles` injections supply conversion and freshness callbacks, and their removal restores unavailable guidance. Registrations and tab retention follow effect lifetimes. The [conversion service](../../document/office-to-pdf/README.md) owns the Host Remote methods, mounted by `api/remotes`.
|
|
117
|
+
|
|
118
|
+
The shared `documentFileBytes()` helper decodes ordinary file and converted PDF responses into one owned byte buffer without expanding bytes into JavaScript array elements. Renderers borrow retained bytes read-only and copy them before Worker transfer.
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
<a id="navigation"></a>
|
|
123
|
+
## Navigation
|
|
124
|
+
|
|
125
|
+
`ctx.sidebarRight.openResource(address, { params: { line } })` carries a 1-based source line through the `file` parameters. In `text-pages` mode, the owner loads sequential pages until that line or EOF. Plain-text and code renderers expose source-line anchors; Markdown does not. A navigation remains pending while its selected renderer has no anchor and runs if the user switches to plain text or code. Code navigation scrolls the inner source viewport directly. Byte-mode renderers do not consume source-line navigation. Each completed navigation revision is answered once. Opening the same file without `revealIfOpened: false` focuses its existing tab and delivers a new revision.
|
|
126
|
+
|
|
127
|
+
<a id="model-experience"></a>
|
|
128
|
+
## Model Experience
|
|
129
|
+
|
|
130
|
+
None, as the viewer only inserts reviewable text into an unsent draft without submitting to the Agent; it registers no tool, prompt section, or session event.
|
|
131
|
+
|
|
132
|
+
#### KV Cache effect
|
|
133
|
+
|
|
134
|
+
No direct effect. An inserted selection joins the draft the user is already editing, so the cache moves only with the send that follows.
|
|
135
|
+
|
|
136
|
+
## Known Limitations and Deferred Work
|
|
137
|
+
|
|
138
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
139
|
+
- **Preview, not editing.** The viewers provide no editing of their own — the header's edit control hands the address to the editor type — and no shared search interface; a directory address fails with `not-regular-file`. Unknown extensions use the plain-text reader and remain subject to its UTF-8/NUL checks.
|
|
140
|
+
- **Office conversion limits.** The preview does not launch native Office editors or download an engine. Binary `.doc`, `.xls`, and `.ppt` files return no missing-font diagnostics. Conversion fidelity and resource limits belong to the [LibreOffice provider](../../document/office-to-pdf/README.md).
|
|
141
|
+
- **Sequential text and bounded complete files.** Deep source lines require the preceding pages; PDF, HTML, and images require a complete result within the Host's `maxFileBytes` cap.
|
|
142
|
+
- **PDF raster allocation is bounded.** Each page bitmap is capped at 16,777,216 pixels; very large pages or high zoom on high-density displays can still render below device resolution.
|
|
143
|
+
- **Byte-view scroll state is not restored.** PDF, HTML, and images can return to the top when their renderer remounts or reloads; fixed PDF and image zoom can overflow horizontally, and HTML iframe scrolling belongs to its opaque browsing context.
|
|
144
|
+
- **Finite local HTML dependencies.** Only direct classic `.js` and stylesheet `.css` references are packed. Browser-resolved resources retain browser origin and network restrictions; no runtime file-read bridge is exposed to the iframe.
|
|
145
|
+
- **Raw HTML is rendered whole or not at all.** Only a complete block-level run is sanitized and rendered. An inline tag (`<sub>x</sub>` inside a paragraph) and a block opening that closes in a later run (`<div>` … Markdown … `</div>`) stay literal text, because the Markdown parse hands those runs over a tag at a time and rendering one would silently drop the markup or wrap nothing.
|
|
146
|
+
- **Selection line numbers are best-effort.** The rendered selection is reverse-searched in the source text, and only an exactly-once hit yields a line span: an ambiguous or absent match inserts the path alone. A selection longer than 500 characters inserts that header without its content.
|
|
147
|
+
- **Package-local wrap glyphs.** `IconWrapFill16` and `IconNowrapFill16` live in `src/client/icons.tsx` until the shared icon set carries them; their props already match the shared icon contract.
|
|
148
|
+
- **Scroll writes are unthrottled.** Every scroll event records its offset in the store; the line blocks are memoized so the resulting re-render hands React the same elements back.
|
|
149
|
+
- **A failed PDF chunk load requires a page reload.** React caches a rejected lazy import for the page lifetime; ordinary PDF open or render failures remain retryable inside the loaded body.
|
|
150
|
+
|
|
151
|
+
<a id="dev-note"></a>
|
|
152
|
+
### Dev Note
|
|
153
|
+
|
|
154
|
+
<details>
|
|
155
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
156
|
+
|
|
157
|
+
None.
|
|
158
|
+
|
|
159
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "右侧 Sidebar 的文档预览:共享文件加载与控件,可选 Markdown、代码、图片、视频、PDF、表格、Office 和 HTML 渲染器,并以纯文本兜底。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @qilin-agent/client-ui-sidebar-documentpreview
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
在右侧 Sidebar 预览可读文件,无需另开 tab 即可切换已注册的渲染器。Markdown 和代码接收累计文本页;PDF、HTML、常见图片和表格接收完整字节;未知文件扩展名使用纯文本。Word 与 PowerPoint 文档在本地转换为 PDF;表格在浏览器内打开。视频从宿主媒体路由以 HTTP Range 流式播放,拖动进度条不经过文件读取器。tab 负责加载、文件状态、渲染器选择、换行以及自动或手动重新载入,文档正文通过同一元数据注册表与子 slot 注册。Sidebar tab 的 kind 为 `text`。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [注册了什么](#what-it-registers)
|
|
17
|
+
- [地址](#addresses)
|
|
18
|
+
- [怎么读](#how-it-reads)
|
|
19
|
+
- [Excel 预览](#excel-preview)
|
|
20
|
+
- [Office 预览](#office-preview)
|
|
21
|
+
- [导航](#navigation)
|
|
22
|
+
- [模型体验](#model-experience)
|
|
23
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
24
|
+
- [开发备注](#dev-note)
|
|
25
|
+
|
|
26
|
+
-----
|
|
27
|
+
|
|
28
|
+
<a id="what-it-registers"></a>
|
|
29
|
+
## 注册了什么
|
|
30
|
+
|
|
31
|
+
- **类型** —— `ctx.sidebarRightTabs.register(...)`,id 为 `@qilin-agent/client-ui-sidebar-documentpreview`(这个实现在 tab 系统里的唯一键,也是其体注册所用的 key),kind `text`,pattern `qilin-resource://file/**`,档位 `fallback`。`canOpen` 只接受 Session 地址,其中路径可为相对或绝对路径;不认领裸 `absolute` 地址。在 `extension` 或 `builtin` 档以更窄 pattern(比如 `*.png`)注册的类型接走那些地址;其他受支持文件落到这里。整个地址就是内容身份,所以不同目录下同名的两个文件、或同一路径在两个会话之下,是两个 tab;解码后的 basename 是 tab 标题,keyed slot `sidebar.right.pane.tab.title` 会在标题前放置按扩展名选择的 `FileTypeIcon`。
|
|
32
|
+
- **正文** —— keyed slot `sidebar.right.pane.tab`,键为类型的 id。固定头部在可用时显示 Host 的绝对路径,否则显示请求路径;目录使用三级标签色,文件名使用一级标签色,路径过长时保留末段并向开头淡出,提示中仍提供完整值。有多个受支持的渲染器时才显示下拉菜单。仅文本兼容的源文件提供纯文本选项;只有一个渲染器时不显示查看器控件。已知的二进制容器后缀没有注册渲染器时,在路径头部下方显示文件类型图标和不支持预览的说明,并且不会发起读取。仅当所选渲染器声明 `wrap: true` 时显示换行开关;图标表示点击后切换到的模式,该偏好按 tab 保存,初始开启。重新载入仍在此头部,不放入 Sidebar 的 tab 条。自动刷新默认开启;独立开关暂时隐藏,状态、文案、样式与切换逻辑保留。正文贴合格的每条边,各渲染器自行提供内容留白,并可拥有内部滚动区。这与 Files tab 右侧预留 2px 滚动条间距的布局有意不同:Preview 使用格的完整宽度,使贴边 HTML 与代码滚动区终止于格的边缘。在文本正文中选中文字会在选区上方锚定一个**加入对话**按钮;点击后把一个围栏块追加到该 tab 所属 Session 的输入框草稿末尾,其信息行为查看器路径加上所选行范围,作为一次可撤销的编辑,草稿中已有的文字与引用芯片保持不变。持有字节而非文件自身文本的正文——PDF、图片、Office、HTML、视频——不提供该选区操作,因为渲染出的选区无法反查源行号。
|
|
33
|
+
- **文件席位** —— 该正文注册的三个子 slot 把被预览的文件交给其他插件。渲染在路径行末尾的 list 席位 `sidebar.right.tab.document.actions`,与渲染在不支持预览的说明下方的 `sidebar.right.tab.document.unpreviewable`,都接收 `{ absolutePath }`,并在 Host 报告文件的绝对路径之前不渲染,因此把文件交给 Host 的贡献永远不会自己臆造路径。keyed 席位 `sidebar.right.tab.document.action` 在重新载入按钮之前放置渲染器专属控件,接收当前 `content` 与所在 tab 的 `useTabInfo` 钩子上下文。`qilin-client-ui-open-in-app` 的文件打开器占用这两个 list 席位。
|
|
34
|
+
- **共享加载与视图状态**,会话作用域、按 tab id 分桶。store 持有累计页或完整字节、读取与观察版本、待处理 Resource 变化、自动刷新状态、加载/失败状态、渲染器选择、滚动位置、换行和已响应的导航 revision。普通 inject face 调用 Remote 读取,并经声明的 store action 写入。重新载入和加载模式变化会淘汰旧请求;tab 的中止信号清理其状态。
|
|
35
|
+
|
|
36
|
+
文档实现在 `ctx.documentPreviews.register({ id, extensions, binaryExtensions?, priority, title, loading, wrap? })` 注册元数据,并以相同 `id` 向 keyed、Session 作用域的子 slot `sidebar.right.tab.document` 注册正文。`binaryExtensions` 列出 `extensions` 中不可按文本阅读的后缀,这些后缀不提供纯文本选项。两处注册都由 effect 持有,通过 `ctx.slots.inject` 等待子 slot。正文接收 `resourceAddress`、`content`、`wrap`、`scrollportRef`、`addResource`、`setResources` 和标准 `useTabInfo`/`useResource` 钩子。依赖成员由 tab 持有的 `ResourceGroup` 管理;替换依赖始终保留根 Resource。内部滚动元素挂载 `scrollportRef`;卸载时恢复共享正文的滚动职责。注册表保留所有匹配备选:`extension`(默认)优先于 `builtin`,随后按更长的后缀、再按注册顺序排列。所选实现仍可用时,下拉选择保持不变。HTML、SVG 和未匹配的扩展名保留纯文本回退,与加载方式无关。
|
|
37
|
+
|
|
38
|
+
`loading: 'text-pages'` 和 `'bytes-complete'` 使用共享文件读取器。选择 `'renderer'` 时,所选正文在读取任何字节前挂载,并接收 `content: { kind: 'renderer', revision, loaded, failed, reload }`。其注入回调负责内容加载、错误和取消。`loaded(version)` 记录已展示的源版本并结束加载。`failed()` 结束加载但不记录成功版本,使后续文件变化可以重试;已被替换的 revision 所发出的报告会被忽略。`reload()` 增加 revision,正文据此取消并替换当前请求。正文也在卸载和 tab 关闭时取消请求,将已完成内容保留在自己声明的 tab store 中,并在 tab 结束时释放。[Office 预览](#office-preview) 使用此模式,转换后的字节和字体元数据不会进入共享文件 store。
|
|
39
|
+
|
|
40
|
+
<a id="addresses"></a>
|
|
41
|
+
## 地址
|
|
42
|
+
|
|
43
|
+
tab 使用 `fileAddressFor` 构造的 Session 地址,携带相对或绝对路径。`hostFileOf(address)` 仅从地址取得 Session,不接收外部 Session 参数,也不借用当前或 Tab Session。Host 通过 Session 文件系统解析文件及关联路径,由该后端控制读取权限。任何 UI(包括 Global 组件)都共享同一完整地址的元数据。[Workspace Files README](../../api/workspace-files/README.zh.md)定义这些规则;渲染器选择不改变导航地址。
|
|
44
|
+
|
|
45
|
+
全部文本分页加载完成后,Markdown 图片通过已鉴权的 `/api/file` 路由读取绝对文件路径,以及相对于源文档所在目录的路径。作者写入的原始 HTML 由本预览自己的消毒器渲染:完整的块级 run 过 DOMPurify 并在其默认之上叠加 denylist,锚点强制新标签打开并带 `noopener`,本地图片源走与 Markdown 图片相同的 `/api/file` 路由。行内 run(解析器每次只交出标签本身)与闭合标签落在另一个 run 的块级开头都保持字面文本,被 denylist 全部拒绝的 run 也不渲染。`ui-primitives` 保持其更严格的默认行为,是预览这一侧主动选择加入。相对图片等待 Host 返回文档绝对路径。URL 转义只解码一次;查询参数与片段后缀不计入文件名。HTTP(S) 图片保留原始 URL,加载失败的图片显示 alt 文本。本地图片要求应用基址为 HTTP(S) URL;图片文件不加入自动刷新依赖。
|
|
46
|
+
|
|
47
|
+
<a id="how-it-reads"></a>
|
|
48
|
+
## 怎么读
|
|
49
|
+
|
|
50
|
+
代码工作工具决定 HTML 预览策略。渲染器通过其注册项的 inject hooks 接收 `interactivePreview`,其来源是共享的代码工作工具偏好。关闭时使用经 DOMPurify 清理的完整静态文档,放在没有任何沙箱权限的 iframe 中:CSP 阻止脚本、外部资源、连接、表单和嵌套框架;重新解析前移除全部 `href` 与 `xlink:href` 属性、刷新指令和声明式 shadow root。内联样式与 data 图片仍然可见,并且不读取关联文件。开启时使用下文描述的脚本化 Blob 预览。切换模式会卸载旧 frame 并中止其待处理的关联文件读取。静态预览释放 CSS/JS 资源订阅,但继续监听根文件。其它文档格式各自保留策略。
|
|
51
|
+
|
|
52
|
+
两种 HTML 模式都把 iframe 名称设为 `qilin-sidebar-html-<tab-id>`,让 frame 拥有与其 tab 绑定的稳定标识;该名称不会让预览获得父文档的访问权。
|
|
53
|
+
|
|
54
|
+
正文通过 `useTabInfo().tab` 读取记录、导航和生命周期。`useResource<'file'>(tab.contentId)` 提供元数据,普通 inject 回调提供内容读取:
|
|
55
|
+
|
|
56
|
+
- 资源快照仅包含 `status`、`value` 和 `failure`;`value` 是 `WorkspaceFileStat` 元数据。提供方可用后,内容读取无需等待首个元数据帧。观察失败优先于 Preview 的变更提示显示;元数据不可用时保留已加载内容。
|
|
57
|
+
- **文本页** —— 纯文本、Markdown 和代码通过 inject 回调调用 `remote.workspaceFiles.read(sessionId, path, { offset }, signal)`。首次挂载读取第一页;滚动到正文末尾或点击 **加载更多** 会读取下一页,直到 `eof`。owner 以 `{ kind: 'text', text, pages, eof }` 提供累计前缀,包含源码行偏移和行数。Markdown 和代码增量渲染此前缀,不把每页当成独立文档。第一页之后到达的更新版本页会使读取从头开始,避免混合版本。尚无内容时,失败会以文件类型图标、说明与重试按钮填满正文;较晚的失败保留已有内容并在其下提供重试。
|
|
58
|
+
- **完整字节** —— PDF、HTML 和常见图片通过 inject 回调调用 `remote.workspaceFiles.readAll(sessionId, path, signal)`。`rpc.ts` 将线路上的 base64 解码为 `data: Uint8Array<ArrayBuffer>`,供 `{ kind: 'bytes', data }` 使用。Host 的 `maxFileBytes` 上限拒绝超大文件,不截断。PDF 在传给 worker 前复制保留的字节,使 Preview 缓冲区仍可使用。字节仅保存在临时视图状态中,绝不进入持久布局或 Session JSONL。加载模式变化会淘汰先前结果。
|
|
59
|
+
- **重新载入** —— 手动重新载入仅让当前 Preview tab 通过自己的 Remote 回调重读,保留滚动偏好并淘汰旧请求。ResourceGroup 成员变化后,自动刷新使用相同回调。各成员首次元数据仅建立基线,不触发重新载入或首读版本对账;后续在读取期间收到的变化仍会留待下一次刷新。读取既不刷新共享元数据,也不清除其它 tab 的提示。
|
|
60
|
+
|
|
61
|
+
开启代码工作工具时,HTML 以贴合正文四边的 Blob iframe 运行,沙箱属性严格为 `sandbox="allow-scripts"`,不含 `allow-same-origin`;脚本无法访问父应用的源或文件读取接口。渲染器通过普通 inject 回调调用 `remote.workspaceFiles.readRelated`,加载直接声明的相对 `.js` 经典脚本和 `.css` 样式表;固定安全上限为单个资源 4 MiB、总计 32 MiB、64 个不同资源。Host 代码解析关联路径,`rpc.ts` 解码返回的字节。依赖 Resource 在 `readRelated` 返回后加入,使用返回的 `absolutePath`,失败时则使用字符串类型的 `error.details.path`;没有 Host 路径时,Client 不自行猜测。在渲染器内部,base64 仅用于把 iframe 引导载荷嵌入脚本文本。`<base href>` 将依赖解析交给浏览器,HTTPS 资源也由浏览器处理。本地模块 import、CSS `url()`/`@import` 和动态 `fetch` 不使用 Host 文件访问。读取失败、无效 UTF-8 或超出上限都使预览失败,不发布部分资源包。替换或卸载文档会释放其 Blob URL。
|
|
62
|
+
|
|
63
|
+
PNG、JPEG、GIF、WebP、BMP、ICO 和 SVG 通过 Blob URL 在 `<img>` 静态图片上下文中渲染,带 12px 内边距和圆角。图片默认适应宽度,但不会放大小于面板的内容;100% 使用图片的固有 CSS 像素宽度。共享缩放控件可产生横向和纵向滚动,但不提供拖拽平移。缩放不会替换 `<img>` 或 Blob URL,因此动画图片会继续播放。位图超过固有尺寸后可能变虚,SVG 则继续使用浏览器的矢量渲染路径。SVG 标记绝不进入应用 DOM 或 iframe,因此其中的脚本无法执行,也无法访问父页面。替换或卸载图片会撤销其 Blob URL。
|
|
64
|
+
|
|
65
|
+
视频扩展名(`mp4`、`webm`、`mov`、`mkv` 及声明列表的其余部分)通过 `<video>` 元素流式播放,其源指向该会话文件经鉴权的 `/sidebar/media` 路由;HTTP Range 窗口应答拖动进度条的 seek,播放从不依赖整文件读取器及其字节上限。TypeScript 的 `.ts` 因与源码扩展名冲突保持不认领,MPEG-TS 由 `m2ts` 覆盖。该渲染器不声明换行开关,并在 `sidebarVideo` 下持有自己的本地化文案。
|
|
66
|
+
|
|
67
|
+
共享文案来自 `sidebarDocumentPreview`;各内置渲染器拥有自己的本地化标签。PDF 与转换后的 Office 预览在浅色模式下使用石墨灰底色,在深色模式下使用哑黑底色,页面带有轻微阴影并保留文档原色。PDF 与图片预览共用浮动控件,提供**适应宽度**、25%、50%、100%、150% 和 200% 选项,并在 25%–400% 固定范围内按 25% 逐级缩放。适应宽度是默认模式并跟随面板尺寸;100% 表示 PDF 的 96 DPI 页面尺寸或图片的固有 CSS 像素尺寸。在精细指针设备上,控件初始隐藏;指针进入底部 72 个 CSS 像素时向上滑入,离开 420 ms 后向下退出;控件悬停、键盘焦点、菜单展开和手势进行期间保持显示,无 hover 的设备则始终显示。Chromium 将 macOS 触控板捏合映射到与 Ctrl+滚轮相同的实时指针锚定路径;控件同步显示手势比例,静止后的值在正文重新挂载后继续保留,直至 tab 关闭。HTML 预览不显示缩放控件。缩放稳定或适应宽度发生变化后,视口附近的 PDF 页面按最终缩放比例乘以设备像素比重绘。新位图完成前保留原有画面和可选文字层;屏幕外的页面在接近视口时更新。
|
|
68
|
+
|
|
69
|
+
首次读取、追加页及 HTML/PDF/图片准备共用仅图标的加载 spinner,其标签暴露给辅助技术,并遵循减少动态效果偏好;内容出现前的每个等待都把 spinner 居中在面板中,打开文件到正文出现始终是同一位置的一个 spinner。下一页加载期间保留已显示的内容。PDF 正文仅在 PDF 预览挂载时加载包内 `client.pdf.js` chunk;PDF.js、Worker 源码和内嵌支持数据不会进入启动 `client.js`。PDF 页面贴边占满面板宽度,组成一个纵向连续序列并在接近视口时惰性渲染;未渲染的页以安静的 3:4 占位块保持位置。PDF.js 官方 TextLayerBuilder 在与画面重合的文字层上管理选区边界和复制文本规范化。配套样式不高亮空白换行;对齐同时考虑 PDF 页面单位、页面旋转与视口宽度变化,页面释放时取消两层渲染。纯图片 PDF 不包含可选取的文字。代码预览默认显示源码行号,但复制文本不包含行号;纯文本与代码使用相同字号和行高。代码直接坐在分栏自身的背景上,而不是会话卡片的填充色;复制条与占满剩余高度的内部滚动区相邻,因此横纵滚动条都从复制控件下方开始。
|
|
70
|
+
|
|
71
|
+
<a id="excel-preview"></a>
|
|
72
|
+
## Excel 预览
|
|
73
|
+
|
|
74
|
+
直接在浏览器中打开 `.xlsx`、`.xls`、`.csv` 和 `.tsv`,支持工作表标签、单元格选择、复制和只读公式栏。XLSX 保留字体、纯色填充、边框、数字格式、富文本、合并单元格、行列尺寸、隐藏行列及工作表,以及冻结标题。XLS 保留已保存的值、公式、数字格式、合并及可用的行列元数据;不支持字体、边框和冻结窗格。工作簿显示已保存的公式结果而不重新计算;缺少的结果保持为空,公式栏旁的紧凑提示会标记显示结果可能不完整或不准确的工作簿。工作簿视图填满预览面板,并随面板尺寸变化自动调整,无需重新加载工作表或选择状态。工作表标签从左侧边缘开始排列;标签溢出时可用触控板横向手势或左右按钮滚动,缩放控件独立占位。表格控件在应用的明暗两种主题下均保持浅色背景和深色文字。触控板的像素位移可同时平移表格的横纵两个方向,包括斜向平移,移动距离跟随手势;冻结标题保持固定。到达工作表边缘后反向移动,仍会保留下一次手势的位移。隐藏选区统计栏及无效的工作表菜单箭头。冻结标题在滚动时保持固定,不显示冻结分隔线和拖拽条。表格预览不调用 Office 转换服务。
|
|
75
|
+
|
|
76
|
+
XLSX 预览在解析前从内存副本中移除 DrawingML 部件,并忽略工作表的绘图引用。原文件和工作表 XML 保持不变。表格上方的提示列出实际检测到但未展示的图表、图片、形状和条件格式,并建议使用系统应用打开工作簿。未检测到这些内容的文件不显示此提示;公式提示保持独立。
|
|
77
|
+
|
|
78
|
+
CSV 和 TSV 默认使用表格查看器,也可选择纯文本;CSV 还提供带语法高亮的 Code 预览。两者分别以逗号和制表符分隔字段,支持引号内分隔符、转义引号、多行字段、空字段和不等长行。首行仍作为数据。值保持为字面字符串,包括前导零、日期、布尔值和类似公式的文本。文本文件支持 UTF-8 或带 BOM 的 UTF-16;无效编码会显示转换提示。引号字段格式错误会使表格预览失败,不会静默丢弃数据。
|
|
79
|
+
|
|
80
|
+
在同一个 `ui-sidebar-documentpreview` 条目上配置 `excel`。这些限制补充 Host 的完整文件读取限制,但不限制浏览器进程内存或解压分配量。
|
|
81
|
+
|
|
82
|
+
| 字段 | 默认值 | 含义 |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `excel.maxBytes` | `16777216`(16 MiB) | 最大源文件字节数 |
|
|
85
|
+
| `excel.maxCells` | `250000` | 所有工作表矩形区域的最大合计单元格数,包含空单元格 |
|
|
86
|
+
| `excel.timeoutMs` | `15000` | 解析 Worker 的最长存活时间,单位毫秒 |
|
|
87
|
+
|
|
88
|
+
惰性 Excel chunk 打包 FortuneSheet、用于 XLSX 的 ExcelJS、用于 XLS 的 SheetJS CE,以及用于 CSV/TSV 的 PapaParse。包内独立于 React 的适配层将解析结果直接映射为 FortuneSheet 单元格,并复用单元格格式化和初始选区。第三方许可证文本保留在发布的 chunk 中;SheetJS CE 保留其 Apache-2.0 条款。每次解析拥有一个独立可释放的 Worker,并传输所保留文件字节的副本;内容替换、卸载、失败或超时都会终止该 Worker。样式表仅作用于 Excel 预览区域。暂不支持图表、绘图/图片、数据透视表、条件格式、编辑、重新计算和导出;字体可用性、Excel 列宽近似和主题色明暗近似会影响保真度。超链接显示为文本,不加载目标地址。ExcelJS 会再次解码 XLSX 字符串公式缓存结果中的实体写法;保存的字面文本 `<` 会显示为 `<`。
|
|
89
|
+
|
|
90
|
+
只读公式栏以单行按字面显示公式和单元格文本,长内容可横向滚动。复制时保留 HTML 表格,并转义单元格内容,包括已保存的公式结果。升级 FortuneSheet 时必须保留这些行为与工作表选区。
|
|
91
|
+
|
|
92
|
+
固定版本的 [ExcelJS 补丁](../../../patches/exceljs@4.4.0.patch)通过包内关系解析工作簿、样式、共享字符串、工作表、批注、Table 和 VML,支持绝对及相对目标,以及 ASCII 大小写等价的部件名,并按命名空间 URI 识别 SpreadsheetML 和 VML 名称。Strict OOXML 的 SpreadsheetML 和关系 URI 映射到相同的已支持预览功能;这不代表完整支持 Strict 标准。XML 部件支持 UTF-8 及两种字节序的 UTF-16;CDATA 按字面文本读取。绘图和条件格式提示按关系查找内容,不依赖部件目录。未被引用的 `xl/drawings/*.xml` 部件及其关系文件也会被移除,但不会产生提示;批注 VML 保持不变。缺失引用部件或存在大小写等价的歧义 ZIP 条目会使预览失败。解析保留批注和 Table 元数据,但预览器没有专门展示它们的控件。补丁同时覆盖 Node 源码和 `dist/exceljs.js`,浏览器入口选择已修补的 bundle。升级依赖时必须保留两个入口的行为,并通过 `tests/excel-opc.client.spec.ts` 与 `tests/excel-xml.client.spec.ts` 中的独立写入器回归测试。
|
|
93
|
+
|
|
94
|
+
<a id="office-preview"></a>
|
|
95
|
+
## Office 预览
|
|
96
|
+
|
|
97
|
+
将 `.doc`、`.docx`、`.ppt` 和 `.pptx` 打开为 PDF 预览,使用与 PDF 文件相同的加载状态、缩放控件、取消和文本选择能力。[Host 提供方](../../document/office-to-pdf/README.zh.md)负责本地转换,并为其他消费者保留电子表格转换 API;无效文件、转换失败和超时会显示本地化消息。缺少 Host 服务时显示配置引导。
|
|
98
|
+
|
|
99
|
+
[Web bundle](../../bundle/web-app/README.zh.md) 以 `ui-sidebar-documentpreview` 挂载本包。通过该条目的 `office` 设置配置临时 Office 缓存;[配置目录](../../../docs/config-catalog.zh.md#qilin-agentclient-ui-sidebar-documentpreview)定义可接受的值。设置注入到每个页面;修改 YAML 后重新加载浏览器页面。
|
|
100
|
+
|
|
101
|
+
| 字段 | 默认值 | 含义 |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `office.maxCachedEntries` | `8` | 最多保留的已完成 PDF 数量 |
|
|
104
|
+
| `office.maxCachedBytes` | `67108864`(64 MiB) | 最多保留的二进制 PDF 字节数,按缓冲区 `byteLength` 计算 |
|
|
105
|
+
| `office.maxPending` / `office.maxReaders` | `8` / `32` | 未完成转换 RPC 数量 / 含元数据查询的读取方数量 |
|
|
106
|
+
|
|
107
|
+
打开 Office 文件时按需请求转换。每次读取都先检查渲染 generation 和已授权的源文件元数据,再共享进行中的转换或复用成功的 PDF。缓存以渲染 generation、Session、源文件绝对路径和源版本作为身份;通过淘汰最久未使用的 PDF,使保留量符合两项限制。失败和超过字节限制的 PDF 不会被保留。转换后 PDF 的返回渲染 generation 与缓存身份一致时,才会保留该 PDF。取消一个读取方后,共享转换会继续运行,直到最后一个读取方离开。连接重置会清空缓存字节并取消待完成读取;插件卸载还会等待未结束的请求完成。PDF 从不持久化;传输字符串、解码存储和 PDF.js 渲染内存不计入缓存限制。
|
|
108
|
+
|
|
109
|
+
后台请求为前台工作保留最后一个在途请求槽和读者槽;将任一限额设为一会拒绝后台读取。渲染器替换后会重新执行一次代次查询与授权检查。重试期间再次替换会显示本地化的繁忙提示。
|
|
110
|
+
|
|
111
|
+
黄色提示条说明转换时不可用的字体。“显示更多”打开锚定字体列表;关闭列表保留提示条。关闭提示条会收起其占位并使 PDF 上移,关闭状态仅在预览持续挂载且源文件版本相同时保留;切换到其他标签再切回会重新显示提示条。过长提示文字在操作按钮前渐隐,减少动态效果偏好会禁用收起动画。
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary>Office 实现——点击展开</summary>
|
|
115
|
+
|
|
116
|
+
Office 注册、加载、缓存和字体提示位于 `src/client/office/`。Office 正文持有转换后的 PDF 字节和字体元数据,并声明嵌套 PDF slot,复用惰性 PDF 正文及其 tab 阅读状态。字体提示位于 Office 滚动区上方。Host 渲染器缺失时,注册仍然可用;可选的 `remote.officeToPdf` 和 `remote.workspaceFiles` 注入提供转换与版本检查回调,移除后恢复不可用提示。注册和 tab 状态保留都遵循 effect 生命周期。[转换服务](../../document/office-to-pdf/README.zh.md)拥有 Host Remote 方法,由 `api/remotes` 挂载。
|
|
117
|
+
|
|
118
|
+
共享 `documentFileBytes()` 辅助函数将普通文件与转换后 PDF 的响应解码到一个独立持有的字节缓冲区,不会将字节展开为 JavaScript 数组元素。渲染器以只读方式借用保留的字节,并在传给 Worker 前复制。
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
<a id="navigation"></a>
|
|
123
|
+
## 导航
|
|
124
|
+
|
|
125
|
+
`ctx.sidebarRight.openResource(address, { params: { line } })` 通过 `file` 参数携带 1 起算的源码行号。在 `text-pages` 模式下,owner 顺序加载到该行或 EOF。纯文本与代码渲染器提供源码行锚点;Markdown 不提供。所选渲染器没有锚点时,导航保持待处理;用户切换到纯文本或代码后执行。代码导航直接滚动内部源码视口。字节模式渲染器不消费源码行导航。每个完成的导航 revision 只响应一次。不带 `revealIfOpened: false` 打开同一文件时聚焦已有 tab,并送达新 revision。
|
|
126
|
+
|
|
127
|
+
<a id="model-experience"></a>
|
|
128
|
+
## 模型体验
|
|
129
|
+
|
|
130
|
+
无,因为查看器只把可审阅的文本插入未发送的草稿,不向 Agent 提交;它不注册工具、提示词段或会话事件。
|
|
131
|
+
|
|
132
|
+
#### KV Cache 影响
|
|
133
|
+
|
|
134
|
+
无直接影响;插入的选区加入用户正在编辑的草稿,缓存只随之后的发送变化。
|
|
135
|
+
|
|
136
|
+
## 已知限制与延期工作
|
|
137
|
+
|
|
138
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
139
|
+
- **预览而非编辑。** 查看器自身不做编辑——头部的编辑控件把地址交给编辑器类型——也没有共享搜索接口;目录地址以 `not-regular-file` 失败。未知扩展名使用纯文本读取,仍受其 UTF-8/NUL 检查限制。
|
|
140
|
+
- **Office 转换限制。** 预览不启动原生 Office 编辑器,也不下载引擎。二进制 `.doc`、`.xls` 和 `.ppt` 文件不返回缺失字体诊断。转换保真度与资源限制由 [LibreOffice 提供方](../../document/office-to-pdf/README.zh.md)负责。
|
|
141
|
+
- **文本顺序分页,完整文件受限。** 定位深处源码行需要先加载此前各页;PDF、HTML 和图片必须取得 Host `maxFileBytes` 上限内的完整结果。
|
|
142
|
+
- **PDF 栅格分配有上限。** 每页位图最多为 16,777,216 像素;超大页面或高像素密度屏幕上的高比例缩放仍可能低于设备分辨率。
|
|
143
|
+
- **字节视图不恢复滚动位置。** PDF、HTML 与图片的渲染器重新挂载或重新载入时可能回到顶部;固定 PDF 与图片缩放可产生横向滚动,HTML iframe 的滚动属于其不透明浏览上下文。
|
|
144
|
+
- **本地 HTML 依赖集合有限。** 只打包直接引用的经典 `.js` 脚本和 `.css` 样式表。浏览器解析的资源仍受浏览器源与网络规则限制;iframe 不获得运行时文件读取桥接。
|
|
145
|
+
- **原始 HTML 要么整块渲染,要么不渲染。** 只有完整的块级 run 会被消毒并渲染。行内标签(段落里的 `<sub>x</sub>`)与闭合标签落在后续 run 的块级开头(`<div>` … Markdown … `</div>`)都保持字面文本:Markdown 解析逐标签交出这些 run,渲染其中单个标签会静默丢掉标记或包住空气。
|
|
146
|
+
- **选区行号是尽力而为。** 渲染出的选区会在源文本中反查,只有恰好命中一次才给出行范围:命中歧义或未命中时只插入路径。超过 500 字符的选区只插入该信息行,不插入内容。
|
|
147
|
+
- **换行图标为包内自绘。** `IconWrapFill16` 与 `IconNowrapFill16` 住在 `src/client/icons.tsx`,直到共享图标集提供为止;它们的 props 已与共享图标契约一致。
|
|
148
|
+
- **滚动写入未节流。** 每次滚动事件都把偏移记进 store;行块已 memo 化,于是由此引发的重渲染交还给 React 的是同一批元素。
|
|
149
|
+
- **PDF chunk 加载失败后需要刷新页面。** React 会在页面生命周期内缓存被拒绝的 lazy import;已加载正文中的普通 PDF 打开或渲染失败仍可重试。
|
|
150
|
+
|
|
151
|
+
<a id="dev-note"></a>
|
|
152
|
+
### 开发备注
|
|
153
|
+
|
|
154
|
+
<details>
|
|
155
|
+
<summary>维护者工作上下文——点击展开</summary>
|
|
156
|
+
|
|
157
|
+
无。
|
|
158
|
+
|
|
159
|
+
</details>
|