@khorsheed/dsh-reader 0.3.0 → 0.3.2

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.en.md +26 -121
  3. package/README.i18n.yaml +3 -3
  4. package/README.md +26 -121
  5. package/cordis.patch.yml +7 -0
  6. package/lib/client.js +29105 -6692
  7. package/lib/index.js +3500 -321
  8. package/lib/typert.host.js +2145 -259
  9. package/lib/typert.remote-client.d.ts +97 -3
  10. package/lib/typert.remote-client.js +1590 -250
  11. package/lib/types/captions.d.ts +99 -0
  12. package/lib/types/captions.js +236 -0
  13. package/lib/types/client/MediaPlayer.d.ts +55 -0
  14. package/lib/types/client/MediaPlayer.js +106 -0
  15. package/lib/types/client/NotesView.d.ts +57 -0
  16. package/lib/types/client/NotesView.js +124 -0
  17. package/lib/types/client/ReaderPane.d.ts +20 -3
  18. package/lib/types/client/ReaderPane.js +860 -164
  19. package/lib/types/client/SpeakerLegend.d.ts +18 -0
  20. package/lib/types/client/SpeakerLegend.js +26 -0
  21. package/lib/types/client/canvas-sync.d.ts +84 -0
  22. package/lib/types/client/canvas-sync.js +59 -0
  23. package/lib/types/client/contract.d.ts +130 -3
  24. package/lib/types/client/extract-article.d.ts +38 -1
  25. package/lib/types/client/extract-article.js +561 -33
  26. package/lib/types/client/icons.d.ts +1 -1
  27. package/lib/types/client/icons.js +1 -1
  28. package/lib/types/client/index.js +81 -2
  29. package/lib/types/client/locales.d.ts +279 -11
  30. package/lib/types/client/locales.js +278 -10
  31. package/lib/types/client/notes.d.ts +142 -0
  32. package/lib/types/client/notes.js +334 -0
  33. package/lib/types/client/parse-rss.js +40 -0
  34. package/lib/types/client/preset-visibility.d.ts +93 -0
  35. package/lib/types/client/preset-visibility.js +115 -0
  36. package/lib/types/client/selectors.d.ts +5 -0
  37. package/lib/types/client/selectors.js +5 -0
  38. package/lib/types/client/session.d.ts +21 -1
  39. package/lib/types/client/session.js +12 -0
  40. package/lib/types/client/store.js +4 -1
  41. package/lib/types/client/translate.d.ts +26 -1
  42. package/lib/types/client/translate.js +29 -12
  43. package/lib/types/client/useLocalTranscript.d.ts +43 -0
  44. package/lib/types/client/useLocalTranscript.js +129 -0
  45. package/lib/types/client/useMedia.d.ts +68 -0
  46. package/lib/types/client/useMedia.js +299 -0
  47. package/lib/types/client/useMediaStates.d.ts +9 -0
  48. package/lib/types/client/useMediaStates.js +43 -0
  49. package/lib/types/client/useNotes.d.ts +110 -0
  50. package/lib/types/client/useNotes.js +423 -0
  51. package/lib/types/client/usePolish.d.ts +47 -0
  52. package/lib/types/client/usePolish.js +162 -0
  53. package/lib/types/document-fetch.d.ts +106 -0
  54. package/lib/types/document-fetch.js +100 -0
  55. package/lib/types/index.d.ts +15 -0
  56. package/lib/types/index.js +3 -0
  57. package/lib/types/local-transcribe.d.ts +171 -0
  58. package/lib/types/local-transcribe.js +473 -0
  59. package/lib/types/media-host.d.ts +91 -0
  60. package/lib/types/media-host.js +230 -0
  61. package/lib/types/media.d.ts +100 -0
  62. package/lib/types/media.js +207 -0
  63. package/lib/types/notes-types.d.ts +158 -0
  64. package/lib/types/notes-types.js +8 -0
  65. package/lib/types/notes.d.ts +213 -0
  66. package/lib/types/notes.js +718 -0
  67. package/lib/types/polish.d.ts +41 -0
  68. package/lib/types/polish.js +136 -0
  69. package/lib/types/remote.d.ts +118 -0
  70. package/lib/types/remote.js +151 -1
  71. package/lib/types/service.d.ts +81 -6
  72. package/lib/types/service.js +286 -65
  73. package/lib/types/skill.d.ts +20 -0
  74. package/lib/types/skill.js +78 -0
  75. package/lib/types/store.js +30 -8
  76. package/lib/types/tool.d.ts +23 -0
  77. package/lib/types/tool.js +40 -0
  78. package/lib/types/tools.d.ts +69 -0
  79. package/lib/types/tools.js +400 -0
  80. package/lib/types/types.d.ts +90 -2
  81. package/lib/types/types.js +66 -0
  82. package/lib/types/xiaoyuzhou.d.ts +43 -0
  83. package/lib/types/xiaoyuzhou.js +134 -0
  84. package/lib/types/ytdlp.d.ts +111 -0
  85. package/lib/types/ytdlp.js +269 -0
  86. package/package.json +33 -20
  87. package/lib/invariant.js +0 -38
  88. package/lib/types/invariant.d.ts +0 -29
  89. package/lib/types/invariant.js +0 -38
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The notes' wire and storage shapes (see `notes.ts` for the store). Kept in a
3
+ * module of their own so the Remote face, the browser half and the `./types`
4
+ * subpath can name them without pulling in the store's `node:` imports.
5
+ *
6
+ * @module @khorsheed/dsh-reader/notes-types
7
+ */
8
+ /**
9
+ * Where a note sits in the article.
10
+ *
11
+ * `quote` is the source of truth: it is the ORIGINAL text, whichever view the
12
+ * reader selected in. `block` (0-based top-level block, the reading position's
13
+ * own unit) and `start` (characters into that block's original text) are
14
+ * hints that make finding it again cheap; when a refetch changed the article
15
+ * they may be wrong, and the quote is searched for instead.
16
+ */
17
+ export interface ReaderNoteAnchor {
18
+ readonly block: number;
19
+ readonly start?: number;
20
+ readonly quote: string;
21
+ /** The translated text, when the selection was made in the translated view. */
22
+ readonly translated?: string;
23
+ /** A video / audio transcript paragraph's start (seconds): where ▶ plays from. */
24
+ readonly time?: number;
25
+ }
26
+ /**
27
+ * One line under a note: a conclusion a discussion reached, or the reader's
28
+ * own addition. Written by the model only when the reader asks for it.
29
+ */
30
+ export interface ReaderNoteReply {
31
+ readonly by: 'agent' | 'user';
32
+ readonly text: string;
33
+ /** The conversation it came from, so the reader can go back to it. */
34
+ readonly sessionId?: string;
35
+ /** Set when the reader rewrote a line the model wrote. */
36
+ readonly editedFrom?: 'agent';
37
+ readonly at: string;
38
+ }
39
+ /** A canvas card a note (or discussion note) was synced to. */
40
+ export interface ReaderSyncedCard {
41
+ readonly canvasId: string;
42
+ readonly cardId: string;
43
+ /** The canvas's title when it was synced (for the 「已在…」 mark). */
44
+ readonly canvasTitle: string;
45
+ readonly at: string;
46
+ }
47
+ /** One note: an excerpt and, optionally, what the reader thought of it. */
48
+ export interface ReaderNote {
49
+ /** `n1`, `n2`… per article, never reused — the model cites notes by it. */
50
+ readonly id: string;
51
+ readonly anchor: ReaderNoteAnchor;
52
+ readonly comment?: string;
53
+ /** Conclusions and additions, oldest first. */
54
+ readonly replies?: readonly ReaderNoteReply[];
55
+ /** Canvas cards made from it (a later edit does not reach them). */
56
+ readonly syncedTo?: readonly ReaderSyncedCard[];
57
+ readonly createdAt: string;
58
+ readonly updatedAt: string;
59
+ }
60
+ /** One point of a discussion note, with the paragraph it points back to. */
61
+ export interface ReaderDiscussionPoint {
62
+ readonly text: string;
63
+ /** ¶ number (1-based), when the point cites the article. */
64
+ readonly para?: number;
65
+ }
66
+ /**
67
+ * A discussion note: what one conversation about the article came to.
68
+ *
69
+ * `check` is a 「让Agent考考我」 round — not an exam: no score, no retake; its
70
+ * points are what the reader had not yet got right, each with the explanation.
71
+ */
72
+ export interface ReaderDiscussion {
73
+ /** `d1`, `d2`… per article, never reused. */
74
+ readonly id: string;
75
+ readonly kind: 'discussion' | 'check';
76
+ readonly sessionId?: string;
77
+ readonly title: string;
78
+ readonly points: readonly ReaderDiscussionPoint[];
79
+ /** One line for what went fine (「其余 2 题讲到位了」). */
80
+ readonly aside?: string;
81
+ /** Canvas cards made from it. */
82
+ readonly syncedTo?: readonly ReaderSyncedCard[];
83
+ readonly createdAt: string;
84
+ }
85
+ /** One conversation that discussed the article. */
86
+ export interface ReaderEntrySession {
87
+ readonly sessionId: string;
88
+ readonly title?: string;
89
+ readonly lastAt: string;
90
+ }
91
+ /** Everything the reader authored about one article. */
92
+ export interface ReaderEntryNotes {
93
+ readonly entryId: string;
94
+ readonly title: string;
95
+ readonly url?: string;
96
+ /** The source's display label (the feed's name, or the site). */
97
+ readonly source?: string;
98
+ readonly notes: readonly ReaderNote[];
99
+ readonly sessions: readonly ReaderEntrySession[];
100
+ /** Discussion notes, newest first. */
101
+ readonly discussions?: readonly ReaderDiscussion[];
102
+ /** The next note number; ids are never reused, so a deleted n3 stays deleted. */
103
+ readonly nextNote: number;
104
+ /** The next discussion number, never reused either. */
105
+ readonly nextDiscussion?: number;
106
+ readonly updatedAt: string;
107
+ }
108
+ /** One snapshot's index row (the text itself is a file of its own). */
109
+ export interface ReaderSnapshotRecord {
110
+ readonly file: string;
111
+ readonly chars: number;
112
+ readonly capturedAt: string;
113
+ }
114
+ /** The notes document. */
115
+ export interface ReaderNotesDoc {
116
+ readonly version: 1;
117
+ readonly entries: Readonly<Record<string, ReaderEntryNotes>>;
118
+ readonly snapshots: Readonly<Record<string, ReaderSnapshotRecord>>;
119
+ }
120
+ /** One article's text as the browser rendered it, for the model. */
121
+ export interface ReaderEntrySnapshot {
122
+ readonly entryId: string;
123
+ readonly title: string;
124
+ readonly url?: string;
125
+ readonly source?: string;
126
+ /** One string per top-level block; ¶n is `paragraphs[n - 1]`. */
127
+ readonly paragraphs: readonly string[];
128
+ readonly truncated?: boolean;
129
+ readonly capturedAt: string;
130
+ }
131
+ /** What a note write answers. */
132
+ export type ReaderNoteOutcome = 'ok' | 'not-found' | 'empty' | 'full';
133
+ /** The article identity every write carries, so a note never lands on a nameless entry. */
134
+ export interface ReaderEntryIdentity {
135
+ readonly entryId: string;
136
+ readonly title: string;
137
+ readonly url?: string;
138
+ readonly source?: string;
139
+ }
140
+ /** A note's input as the browser sends it, before bounding. */
141
+ export interface ReaderNoteInput extends ReaderEntryIdentity {
142
+ readonly block: number;
143
+ readonly start?: number;
144
+ readonly quote: string;
145
+ readonly translated?: string;
146
+ readonly comment?: string;
147
+ readonly time?: number;
148
+ }
149
+ /** A discussion note's input, before bounding. */
150
+ export interface ReaderDiscussionInput extends ReaderEntryIdentity {
151
+ readonly kind: 'discussion' | 'check';
152
+ readonly sessionId?: string;
153
+ /** The discussion note's own short title (`title` is the article's). */
154
+ readonly heading?: string;
155
+ readonly points: readonly ReaderDiscussionPoint[];
156
+ readonly aside?: string;
157
+ }
158
+ //# sourceMappingURL=notes-types.d.ts.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The notes' wire and storage shapes (see `notes.ts` for the store). Kept in a
3
+ * module of their own so the Remote face, the browser half and the `./types`
4
+ * subpath can name them without pulling in the store's `node:` imports.
5
+ *
6
+ * @module @khorsheed/dsh-reader/notes-types
7
+ */
8
+ export {};
@@ -0,0 +1,213 @@
1
+ /**
2
+ * The reader's notes: excerpts the reader kept from an article, the comment on
3
+ * each, which conversations discussed the article, and the article text the
4
+ * model reads when it does.
5
+ *
6
+ * Notes are what the reader OWNS, so they live on the host next to the rest of
7
+ * the reader's state (the state-boundaries note, rule 1) — but in their own
8
+ * file, `notes.json`, not inside `state.json`'s per-entry annotations. An
9
+ * annotation is shaped around the body cache: `boundAnnotations` evicts by
10
+ * body, `normalizeAnnotations` drops entries that carry nothing it knows. A
11
+ * note must outlive every one of those (the excerpt carries its own text), and
12
+ * keeping it out of that machinery is what makes "the body was evicted" and
13
+ * "the note is gone" two unrelated events.
14
+ *
15
+ * The article text is a second, separate concern: the host never parses an
16
+ * article (the browser half does), and a feed entry's text never reaches the
17
+ * host at all. So the browser hands over a SNAPSHOT — the article's original
18
+ * prose, one string per top-level block, numbered the way the pane numbers its
19
+ * blocks — and the model reads that. Snapshots are capped and pruned; notes
20
+ * are never pruned.
21
+ *
22
+ * Persistence mirrors `store.ts`: native fs at the deployment's state root,
23
+ * atomic temp-file-and-rename writes, and an in-memory document when the root
24
+ * is not writable. Writes are serialized through one promise chain, so two
25
+ * gestures landing together never read the same version and lose one.
26
+ *
27
+ * @module @khorsheed/dsh-reader/notes
28
+ */
29
+ import { ReaderPolishStore } from './polish.ts';
30
+ import type { ReaderPolishView } from './types.ts';
31
+ import type { ReaderDiscussion, ReaderDiscussionInput, ReaderDiscussionPoint, ReaderEntryIdentity, ReaderEntryNotes, ReaderEntrySnapshot, ReaderNote, ReaderNoteInput, ReaderNoteOutcome, ReaderNotesDoc } from './notes-types.ts';
32
+ export type * from './notes-types.ts';
33
+ /** The notes document's file name inside the state root. */
34
+ export declare const READER_NOTES_FILE_NAME = "notes.json";
35
+ /** Where article snapshots live, inside the state root. */
36
+ export declare const READER_SNAPSHOTS_DIR = "snapshots";
37
+ /** Most notes one article keeps (a cap against a runaway client, not a quota anyone reaches). */
38
+ export declare const MAX_NOTES_PER_ENTRY = 500;
39
+ /** Most conversations one article remembers. */
40
+ export declare const MAX_SESSIONS_PER_ENTRY = 50;
41
+ /** Snapshots kept for articles with no notes and no discussions (the most recent ones). */
42
+ export declare const MAX_LOOSE_SNAPSHOTS = 30;
43
+ /** The cap on one snapshot's total characters (a long paper fits; a book does not). */
44
+ export declare const MAX_SNAPSHOT_CHARS = 400000;
45
+ /** Most lines under one note. */
46
+ export declare const MAX_REPLIES_PER_NOTE = 50;
47
+ /** Most discussion notes one article keeps (the oldest go first). */
48
+ export declare const MAX_DISCUSSIONS_PER_ENTRY = 100;
49
+ /** Most points in one discussion note. */
50
+ export declare const MAX_POINTS = 30;
51
+ /** The cap on one reply or one discussion point. */
52
+ export declare const MAX_POINT_CHARS = 4000;
53
+ /** The cap on one excerpt. */
54
+ export declare const MAX_QUOTE_CHARS = 2000;
55
+ /** The cap on one comment. */
56
+ export declare const MAX_COMMENT_CHARS = 4000;
57
+ /** The empty document. */
58
+ export declare function emptyNotesDoc(): ReaderNotesDoc;
59
+ /** Bound a discussion's points: trimmed, capped, ¶ kept only when it is a positive integer. */
60
+ export declare function boundPoints(points: readonly unknown[]): ReaderDiscussionPoint[];
61
+ /**
62
+ * Coerce a parsed value into a notes document, dropping what it cannot read.
63
+ *
64
+ * @param value - the parsed file.
65
+ * @returns the document.
66
+ */
67
+ export declare function normalizeNotesDoc(value: unknown): ReaderNotesDoc;
68
+ /**
69
+ * Bound a snapshot's paragraphs: whitespace collapsed, empty blocks kept as
70
+ * empty strings (so ¶ numbers stay aligned with the pane's blocks), and the
71
+ * whole capped — cut at a paragraph boundary, with the cut reported.
72
+ *
73
+ * @param paragraphs - the blocks' original text.
74
+ * @returns the bounded paragraphs and whether anything was dropped.
75
+ */
76
+ export declare function boundParagraphs(paragraphs: readonly unknown[]): {
77
+ paragraphs: string[];
78
+ truncated: boolean;
79
+ };
80
+ /** The reader's notes store. */
81
+ export declare class ReaderNotesStore {
82
+ private readonly root;
83
+ private readonly file;
84
+ private readonly snapshotsDir;
85
+ private unwritable;
86
+ /** The document of record when the root is not writable. */
87
+ private memoryDoc;
88
+ /** Snapshots kept in memory when the root is not writable. */
89
+ private readonly memorySnapshots;
90
+ /** The write queue: every mutation runs after the previous one settled. */
91
+ private queue;
92
+ /** The agent's 润色 translations, per article (keyed to the snapshot's text). */
93
+ readonly polish: ReaderPolishStore;
94
+ /**
95
+ * @param config - the same state-root override the reader's main store takes.
96
+ */
97
+ constructor(config?: {
98
+ stateRoot?: string;
99
+ });
100
+ /** An article's 润色 layer, when it belongs to the text captured now. */
101
+ polishView(entryId: string): ReaderPolishView | undefined;
102
+ /** Turn one paragraph back to the machine translation, or to the 润色 again. */
103
+ setPolishOff(entryId: string, paragraph: number, off: boolean): ReaderPolishView | undefined;
104
+ /** Whether the notes persist (false means memory-only, for this process). */
105
+ get available(): boolean;
106
+ /** Read the document; a missing or unreadable file reads as empty. */
107
+ read(): ReaderNotesDoc;
108
+ /** Run one read-modify-write after every earlier one. */
109
+ private update;
110
+ /** One article's notes and discussions, oldest note first. */
111
+ entry(entryId: string): ReaderEntryNotes | undefined;
112
+ /** Every article that carries notes or discussions. */
113
+ entries(): ReaderEntryNotes[];
114
+ /** How many notes each article carries (the wall's badge). */
115
+ counts(): Record<string, number>;
116
+ /**
117
+ * Keep one excerpt.
118
+ *
119
+ * @param input - the article, the excerpt and where it sits, and the comment.
120
+ * @returns the new note, or why none was kept.
121
+ */
122
+ addNote(input: ReaderNoteInput): Promise<ReaderNote | 'empty' | 'full'>;
123
+ /** Change (or clear) one note's comment. */
124
+ updateNote(request: {
125
+ entryId: string;
126
+ noteId: string;
127
+ comment: string;
128
+ }): Promise<ReaderNoteOutcome>;
129
+ /** Delete one note (its id is not handed out again). */
130
+ deleteNote(request: {
131
+ entryId: string;
132
+ noteId: string;
133
+ }): Promise<ReaderNoteOutcome>;
134
+ /**
135
+ * Add one line under a note: a conclusion (by the model, when the reader asked
136
+ * for it) or the reader's own addition.
137
+ *
138
+ * @param request - the article, the note, the line and who wrote it.
139
+ */
140
+ addReply(request: {
141
+ entryId: string;
142
+ noteId: string;
143
+ text: string;
144
+ by: 'agent' | 'user';
145
+ sessionId?: string;
146
+ }): Promise<ReaderNoteOutcome>;
147
+ /**
148
+ * Rewrite (or, with empty text, remove) one line under a note. A line the
149
+ * model wrote becomes the reader's, marked as rewritten from the model's.
150
+ */
151
+ editReply(request: {
152
+ entryId: string;
153
+ noteId: string;
154
+ index: number;
155
+ text: string;
156
+ }): Promise<ReaderNoteOutcome>;
157
+ /**
158
+ * Keep one discussion note (newest first). The conversation it came from is
159
+ * remembered on the article too.
160
+ *
161
+ * @param input - the article, the kind, the points.
162
+ * @returns the new discussion note, or why none was kept.
163
+ */
164
+ addDiscussion(input: ReaderDiscussionInput): Promise<ReaderDiscussion | 'empty'>;
165
+ /**
166
+ * Remember that a note (or a discussion note) became a canvas card, so the
167
+ * notes face can say where it went and not offer the same sync twice blindly.
168
+ */
169
+ markSynced(request: {
170
+ entryId: string;
171
+ noteId?: string;
172
+ discussionId?: string;
173
+ canvasId: string;
174
+ cardId: string;
175
+ canvasTitle: string;
176
+ }): Promise<ReaderNoteOutcome>;
177
+ /** Delete one discussion note. */
178
+ deleteDiscussion(request: {
179
+ entryId: string;
180
+ discussionId: string;
181
+ }): Promise<ReaderNoteOutcome>;
182
+ /**
183
+ * The article this conversation discussed most recently, when there is one —
184
+ * where a model's write lands when it names no article.
185
+ */
186
+ sessionEntry(sessionId: string): string | undefined;
187
+ /**
188
+ * Remember that a conversation discussed this article (most recent first).
189
+ *
190
+ * @param request - the article and the conversation.
191
+ */
192
+ recordSession(request: ReaderEntryIdentity & {
193
+ sessionId: string;
194
+ sessionTitle?: string;
195
+ }): Promise<'ok' | 'empty'>;
196
+ /**
197
+ * Keep the article's text for the model, replacing the previous snapshot.
198
+ *
199
+ * Loose snapshots (articles with no notes and no discussions) are pruned to
200
+ * the {@link MAX_LOOSE_SNAPSHOTS} most recent; the rest are kept as long as
201
+ * their article carries anything.
202
+ *
203
+ * @param request - the article and its blocks' original text.
204
+ */
205
+ snapshot(request: ReaderEntryIdentity & {
206
+ paragraphs: readonly string[];
207
+ }): Promise<'ok' | 'empty'>;
208
+ /** The article's text as last captured, when there is one. */
209
+ readSnapshot(entryId: string): ReaderEntrySnapshot | undefined;
210
+ /** Every article with a snapshot, newest capture first. */
211
+ snapshotIds(): string[];
212
+ }
213
+ //# sourceMappingURL=notes.d.ts.map