@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,41 @@
1
+ import type { ReaderPolishView } from './types.ts';
2
+ /**
3
+ * What a layer belongs to: the paragraphs' text without their time and
4
+ * speaker prefixes, hashed.
5
+ */
6
+ export declare function sourceKey(paragraphs: readonly string[]): string;
7
+ /**
8
+ * Parse the model's write-back: lines `¶12:译文…` (a line without a ¶
9
+ * continues the paragraph before it).
10
+ *
11
+ * @param text - what the model sent.
12
+ */
13
+ export declare function parsePolish(text: string): Map<number, string>;
14
+ /** The per-article 润色 layers. */
15
+ export declare class ReaderPolishStore {
16
+ private readonly dir;
17
+ private readonly memory;
18
+ constructor(stateRoot: string);
19
+ private fileOf;
20
+ private load;
21
+ private save;
22
+ /**
23
+ * An article's layer, when it belongs to the text now captured.
24
+ *
25
+ * @param entryId - the article.
26
+ * @param key - the current text's {@link sourceKey}.
27
+ */
28
+ view(entryId: string, key: string | undefined): ReaderPolishView | undefined;
29
+ /**
30
+ * Merge the model's translations into the layer (a different text starts a
31
+ * fresh one).
32
+ *
33
+ * @returns the layer after the write.
34
+ */
35
+ write(entryId: string, key: string, total: number, updates: ReadonlyMap<number, string>): ReaderPolishView;
36
+ /** Turn one paragraph back to (or away from) the machine translation. */
37
+ setOff(entryId: string, key: string | undefined, paragraph: number, off: boolean): ReaderPolishView | undefined;
38
+ /** Forget an article's layer. */
39
+ drop(entryId: string): void;
40
+ }
41
+ //# sourceMappingURL=polish.d.ts.map
@@ -0,0 +1,136 @@
1
+ /**
2
+ * The 润色 layer: an agent's paragraph-by-paragraph translation of an
3
+ * article, written back through `reader_translate` and shown in place of the
4
+ * browser's sentence-by-sentence machine translation.
5
+ *
6
+ * Keyed to the article's text as the model read it (the snapshot): a
7
+ * re-transcription changes the text and so drops the layer; renaming a
8
+ * speaker only changes the "[mm:ss] 名字:" prefixes and keeps it.
9
+ *
10
+ * One JSON file per article under `<state>/polish/`, memory-only when the
11
+ * state root cannot be written.
12
+ *
13
+ * @module @khorsheed/dsh-reader/polish
14
+ */
15
+ import { createHash } from 'node:crypto';
16
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ /** The time / speaker prefix of a transcript paragraph in the snapshot. */
19
+ const PREFIX = /^\[\d+(?::\d+){1,2}\]\s*(?:[^:\n]{1,40}:)?/;
20
+ /**
21
+ * What a layer belongs to: the paragraphs' text without their time and
22
+ * speaker prefixes, hashed.
23
+ */
24
+ export function sourceKey(paragraphs) {
25
+ const hash = createHash('sha1');
26
+ hash.update(String(paragraphs.length));
27
+ for (const paragraph of paragraphs)
28
+ hash.update(`\n${paragraph.replace(PREFIX, '')}`);
29
+ return hash.digest('hex').slice(0, 24);
30
+ }
31
+ /**
32
+ * Parse the model's write-back: lines `¶12:译文…` (a line without a ¶
33
+ * continues the paragraph before it).
34
+ *
35
+ * @param text - what the model sent.
36
+ */
37
+ export function parsePolish(text) {
38
+ const out = new Map();
39
+ let current;
40
+ for (const line of text.split(/\r?\n/)) {
41
+ const head = /^\s*¶\s*(\d+)\s*[::]\s*(.*)$/.exec(line);
42
+ if (head !== null) {
43
+ current = Number(head[1]);
44
+ out.set(current, head[2].trim());
45
+ }
46
+ else if (current !== undefined && line.trim() !== '') {
47
+ out.set(current, `${out.get(current) ?? ''}${line.trim()}`);
48
+ }
49
+ }
50
+ for (const [key, value] of out)
51
+ if (value === '')
52
+ out.delete(key);
53
+ return out;
54
+ }
55
+ /** The per-article 润色 layers. */
56
+ export class ReaderPolishStore {
57
+ dir;
58
+ memory = new Map();
59
+ constructor(stateRoot) {
60
+ this.dir = join(stateRoot, 'polish');
61
+ }
62
+ fileOf(entryId) {
63
+ return join(this.dir, `${createHash('sha1').update(entryId).digest('hex').slice(0, 32)}.json`);
64
+ }
65
+ load(entryId) {
66
+ const cached = this.memory.get(entryId);
67
+ if (cached !== undefined)
68
+ return cached;
69
+ try {
70
+ const parsed = JSON.parse(readFileSync(this.fileOf(entryId), 'utf8'));
71
+ if (parsed.entryId !== entryId)
72
+ return undefined;
73
+ this.memory.set(entryId, parsed);
74
+ return parsed;
75
+ }
76
+ catch {
77
+ return undefined;
78
+ }
79
+ }
80
+ save(file) {
81
+ this.memory.set(file.entryId, file);
82
+ try {
83
+ mkdirSync(this.dir, { recursive: true });
84
+ const target = this.fileOf(file.entryId);
85
+ writeFileSync(`${target}.tmp`, JSON.stringify(file));
86
+ renameSync(`${target}.tmp`, target);
87
+ }
88
+ catch {
89
+ // Memory-only: the layer lasts this process.
90
+ }
91
+ }
92
+ /**
93
+ * An article's layer, when it belongs to the text now captured.
94
+ *
95
+ * @param entryId - the article.
96
+ * @param key - the current text's {@link sourceKey}.
97
+ */
98
+ view(entryId, key) {
99
+ const file = this.load(entryId);
100
+ if (file === undefined || key === undefined || file.sourceKey !== key)
101
+ return undefined;
102
+ const done = Object.keys(file.paragraphs).length;
103
+ return { total: file.total, done, paragraphs: { ...file.paragraphs }, off: [...file.off], updatedAt: file.updatedAt, complete: done >= file.total };
104
+ }
105
+ /**
106
+ * Merge the model's translations into the layer (a different text starts a
107
+ * fresh one).
108
+ *
109
+ * @returns the layer after the write.
110
+ */
111
+ write(entryId, key, total, updates) {
112
+ const before = this.load(entryId);
113
+ const fresh = before === undefined || before.sourceKey !== key;
114
+ const paragraphs = fresh ? {} : { ...before.paragraphs };
115
+ for (const [index, value] of updates)
116
+ if (index >= 1 && index <= total)
117
+ paragraphs[String(index)] = value;
118
+ const file = { entryId, sourceKey: key, total, paragraphs, off: fresh ? [] : before.off, updatedAt: new Date().toISOString() };
119
+ this.save(file);
120
+ return this.view(entryId, key);
121
+ }
122
+ /** Turn one paragraph back to (or away from) the machine translation. */
123
+ setOff(entryId, key, paragraph, off) {
124
+ const file = this.load(entryId);
125
+ if (file === undefined || key === undefined || file.sourceKey !== key)
126
+ return undefined;
127
+ const rest = file.off.filter(item => item !== paragraph);
128
+ this.save({ ...file, off: off ? [...rest, paragraph] : rest });
129
+ return this.view(entryId, key);
130
+ }
131
+ /** Forget an article's layer. */
132
+ drop(entryId) {
133
+ this.memory.delete(entryId);
134
+ rmSync(this.fileOf(entryId), { force: true });
135
+ }
136
+ }
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import type { Context } from '@deepseek-ai/cordis';
13
13
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
14
+ import type { LocalTranscriptStatus, MediaCardStates, ReaderEntryIdentity, ReaderPolishView, ReaderEntryNotes, ReaderMedia, ReaderNote, ReaderNoteInput, ReaderNoteOutcome } from './types.ts';
14
15
  import type { ReaderAddFailure, ReaderAddOutcome, ReaderAddRefusal, ReaderAnnotationOutcome, ReaderBackfillCandidate, ReaderEntryBodyView, ReaderImageResult, ReaderEntryFetchState, ReaderBody, ReaderCapabilities, ReaderEntryTranslationView, ReaderMutationOutcome, ReaderRecentEntry, ReaderRefreshResult, ReaderSentenceLearn, ReaderSourceSummary, ReaderStorageStats, ReaderTag } from './types.ts';
15
16
  declare module '@deepseek-ai/cordis' {
16
17
  interface Context {
@@ -26,6 +27,8 @@ export declare class ReaderRemoteService extends TypertRemoteService<ReaderRemot
26
27
  constructor(ctx: Context, _config?: ReaderRemoteConfig);
27
28
  /** The service core this face delegates to. */
28
29
  private get core();
30
+ /** The notes store this face delegates to. */
31
+ private get notes();
29
32
  /** Capability handshake: what the browser may rely on in this composition. */
30
33
  capabilities(): Promise<ReaderCapabilities>;
31
34
  /** The configured sources, without payloads. */
@@ -227,6 +230,121 @@ export declare class ReaderRemoteService extends TypertRemoteService<ReaderRemot
227
230
  label: string;
228
231
  text: string;
229
232
  }): Promise<'ok' | 'unavailable'>;
233
+ /** One article's notes and the conversations that discussed it. */
234
+ entryNotes(request: {
235
+ entryId: string;
236
+ }): Promise<{
237
+ entry?: ReaderEntryNotes;
238
+ }>;
239
+ /** How many notes each article carries. */
240
+ noteCounts(): Promise<{
241
+ counts: Record<string, number>;
242
+ }>;
243
+ /** Keep one excerpt (and its comment). */
244
+ addNote(request: ReaderNoteInput): Promise<ReaderNote | 'empty' | 'full'>;
245
+ /** Change or clear one note's comment. */
246
+ updateNote(request: {
247
+ entryId: string;
248
+ noteId: string;
249
+ comment: string;
250
+ }): Promise<ReaderNoteOutcome>;
251
+ /** Delete one note. */
252
+ deleteNote(request: {
253
+ entryId: string;
254
+ noteId: string;
255
+ }): Promise<ReaderNoteOutcome>;
256
+ /** Remember that a conversation discussed this article. */
257
+ recordDiscussion(request: ReaderEntryIdentity & {
258
+ sessionId: string;
259
+ sessionTitle?: string;
260
+ }): Promise<'ok' | 'empty'>;
261
+ /** Add the reader's own line under a note. */
262
+ addReply(request: {
263
+ entryId: string;
264
+ noteId: string;
265
+ text: string;
266
+ }): Promise<ReaderNoteOutcome>;
267
+ /** Rewrite (or, with empty text, remove) one line under a note. */
268
+ editReply(request: {
269
+ entryId: string;
270
+ noteId: string;
271
+ index: number;
272
+ text: string;
273
+ }): Promise<ReaderNoteOutcome>;
274
+ /** Remember that a note or a discussion note became a canvas card. */
275
+ markSynced(request: {
276
+ entryId: string;
277
+ noteId?: string;
278
+ discussionId?: string;
279
+ canvasId: string;
280
+ cardId: string;
281
+ canvasTitle: string;
282
+ }): Promise<ReaderNoteOutcome>;
283
+ /** Delete one discussion note. */
284
+ deleteDiscussion(request: {
285
+ entryId: string;
286
+ discussionId: string;
287
+ }): Promise<ReaderNoteOutcome>;
288
+ /** A video entry's transcript from its platform captions (stored; `none` when it has none). */
289
+ mediaTranscript(request: {
290
+ entryId: string;
291
+ url: string;
292
+ }): Promise<{
293
+ entryId: string;
294
+ html?: string;
295
+ captions: 'platform' | 'none' | 'failed';
296
+ language?: string;
297
+ error?: string;
298
+ }>;
299
+ /**
300
+ * Start (or rejoin) local transcription of an entry without captions.
301
+ * Answers `unavailable` without the transcription plugin and `needs-models`
302
+ * until the first model download is confirmed.
303
+ */
304
+ startLocalTranscript(request: {
305
+ entryId: string;
306
+ url: string;
307
+ media: ReaderMedia;
308
+ title?: string;
309
+ confirmModels?: boolean;
310
+ }): Promise<LocalTranscriptStatus>;
311
+ /** Where an entry's local transcription stands, with the transcript so far. */
312
+ localTranscript(request: {
313
+ entryId: string;
314
+ }): Promise<LocalTranscriptStatus>;
315
+ /** The reader's names for a transcript's speakers, by index. */
316
+ speakerNames(request: {
317
+ entryId: string;
318
+ }): Promise<Record<string, string>>;
319
+ /** Name one speaker of a transcript (empty: back to 说话人 A…); answers all the names. */
320
+ nameSpeaker(request: {
321
+ entryId: string;
322
+ speaker: number;
323
+ name: string;
324
+ }): Promise<Record<string, string>>;
325
+ /** The wall's video / audio cards: what each one's transcript is (captions, local, transcribing…). */
326
+ mediaStates(request: {
327
+ entryIds: string[];
328
+ }): Promise<MediaCardStates>;
329
+ /** Leave (withdraws a queued job) or, with `force`, stop an entry's local transcription. */
330
+ cancelLocalTranscript(request: {
331
+ entryId: string;
332
+ force: boolean;
333
+ }): Promise<LocalTranscriptStatus>;
334
+ /** The article's 润色 layer (the agent's translation), when there is one for its current text. */
335
+ polish(request: {
336
+ entryId: string;
337
+ }): Promise<ReaderPolishView | null>;
338
+ /** Show one paragraph's machine translation instead of the 润色 (or back). */
339
+ setPolishOff(request: {
340
+ entryId: string;
341
+ paragraph: number;
342
+ off: boolean;
343
+ }): Promise<ReaderPolishView | null>;
344
+ /** Hand the host the article's original text, block by block, for the model to read. */
345
+ snapshotEntry(request: ReaderEntryIdentity & {
346
+ paragraphs: string[];
347
+ }): Promise<'ok' | 'empty'>;
230
348
  }
231
349
  export default ReaderRemoteService;
232
350
  //# sourceMappingURL=remote.d.ts.map
@@ -69,6 +69,26 @@ let ReaderRemoteService = (() => {
69
69
  let _listRecent_decorators;
70
70
  let _clearRecent_decorators;
71
71
  let _quoteToSideChat_decorators;
72
+ let _entryNotes_decorators;
73
+ let _noteCounts_decorators;
74
+ let _addNote_decorators;
75
+ let _updateNote_decorators;
76
+ let _deleteNote_decorators;
77
+ let _recordDiscussion_decorators;
78
+ let _addReply_decorators;
79
+ let _editReply_decorators;
80
+ let _markSynced_decorators;
81
+ let _deleteDiscussion_decorators;
82
+ let _mediaTranscript_decorators;
83
+ let _startLocalTranscript_decorators;
84
+ let _localTranscript_decorators;
85
+ let _speakerNames_decorators;
86
+ let _nameSpeaker_decorators;
87
+ let _mediaStates_decorators;
88
+ let _cancelLocalTranscript_decorators;
89
+ let _polish_decorators;
90
+ let _setPolishOff_decorators;
91
+ let _snapshotEntry_decorators;
72
92
  return class ReaderRemoteService extends _classSuper {
73
93
  static {
74
94
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
@@ -104,6 +124,26 @@ let ReaderRemoteService = (() => {
104
124
  _listRecent_decorators = [Remote('listRecent')];
105
125
  _clearRecent_decorators = [Remote('clearRecent')];
106
126
  _quoteToSideChat_decorators = [Remote('quoteToSideChat')];
127
+ _entryNotes_decorators = [Remote('entryNotes')];
128
+ _noteCounts_decorators = [Remote('noteCounts')];
129
+ _addNote_decorators = [Remote('addNote')];
130
+ _updateNote_decorators = [Remote('updateNote')];
131
+ _deleteNote_decorators = [Remote('deleteNote')];
132
+ _recordDiscussion_decorators = [Remote('recordDiscussion')];
133
+ _addReply_decorators = [Remote('addReply')];
134
+ _editReply_decorators = [Remote('editReply')];
135
+ _markSynced_decorators = [Remote('markSynced')];
136
+ _deleteDiscussion_decorators = [Remote('deleteDiscussion')];
137
+ _mediaTranscript_decorators = [Remote('mediaTranscript')];
138
+ _startLocalTranscript_decorators = [Remote('startLocalTranscript')];
139
+ _localTranscript_decorators = [Remote('localTranscript')];
140
+ _speakerNames_decorators = [Remote('speakerNames')];
141
+ _nameSpeaker_decorators = [Remote('nameSpeaker')];
142
+ _mediaStates_decorators = [Remote('mediaStates')];
143
+ _cancelLocalTranscript_decorators = [Remote('cancelLocalTranscript')];
144
+ _polish_decorators = [Remote('polish')];
145
+ _setPolishOff_decorators = [Remote('setPolishOff')];
146
+ _snapshotEntry_decorators = [Remote('snapshotEntry')];
107
147
  __esDecorate(this, null, _capabilities_decorators, { kind: "method", name: "capabilities", static: false, private: false, access: { has: obj => "capabilities" in obj, get: obj => obj.capabilities }, metadata: _metadata }, null, _instanceExtraInitializers);
108
148
  __esDecorate(this, null, _listSources_decorators, { kind: "method", name: "listSources", static: false, private: false, access: { has: obj => "listSources" in obj, get: obj => obj.listSources }, metadata: _metadata }, null, _instanceExtraInitializers);
109
149
  __esDecorate(this, null, _addSource_decorators, { kind: "method", name: "addSource", static: false, private: false, access: { has: obj => "addSource" in obj, get: obj => obj.addSource }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -136,9 +176,29 @@ let ReaderRemoteService = (() => {
136
176
  __esDecorate(this, null, _listRecent_decorators, { kind: "method", name: "listRecent", static: false, private: false, access: { has: obj => "listRecent" in obj, get: obj => obj.listRecent }, metadata: _metadata }, null, _instanceExtraInitializers);
137
177
  __esDecorate(this, null, _clearRecent_decorators, { kind: "method", name: "clearRecent", static: false, private: false, access: { has: obj => "clearRecent" in obj, get: obj => obj.clearRecent }, metadata: _metadata }, null, _instanceExtraInitializers);
138
178
  __esDecorate(this, null, _quoteToSideChat_decorators, { kind: "method", name: "quoteToSideChat", static: false, private: false, access: { has: obj => "quoteToSideChat" in obj, get: obj => obj.quoteToSideChat }, metadata: _metadata }, null, _instanceExtraInitializers);
179
+ __esDecorate(this, null, _entryNotes_decorators, { kind: "method", name: "entryNotes", static: false, private: false, access: { has: obj => "entryNotes" in obj, get: obj => obj.entryNotes }, metadata: _metadata }, null, _instanceExtraInitializers);
180
+ __esDecorate(this, null, _noteCounts_decorators, { kind: "method", name: "noteCounts", static: false, private: false, access: { has: obj => "noteCounts" in obj, get: obj => obj.noteCounts }, metadata: _metadata }, null, _instanceExtraInitializers);
181
+ __esDecorate(this, null, _addNote_decorators, { kind: "method", name: "addNote", static: false, private: false, access: { has: obj => "addNote" in obj, get: obj => obj.addNote }, metadata: _metadata }, null, _instanceExtraInitializers);
182
+ __esDecorate(this, null, _updateNote_decorators, { kind: "method", name: "updateNote", static: false, private: false, access: { has: obj => "updateNote" in obj, get: obj => obj.updateNote }, metadata: _metadata }, null, _instanceExtraInitializers);
183
+ __esDecorate(this, null, _deleteNote_decorators, { kind: "method", name: "deleteNote", static: false, private: false, access: { has: obj => "deleteNote" in obj, get: obj => obj.deleteNote }, metadata: _metadata }, null, _instanceExtraInitializers);
184
+ __esDecorate(this, null, _recordDiscussion_decorators, { kind: "method", name: "recordDiscussion", static: false, private: false, access: { has: obj => "recordDiscussion" in obj, get: obj => obj.recordDiscussion }, metadata: _metadata }, null, _instanceExtraInitializers);
185
+ __esDecorate(this, null, _addReply_decorators, { kind: "method", name: "addReply", static: false, private: false, access: { has: obj => "addReply" in obj, get: obj => obj.addReply }, metadata: _metadata }, null, _instanceExtraInitializers);
186
+ __esDecorate(this, null, _editReply_decorators, { kind: "method", name: "editReply", static: false, private: false, access: { has: obj => "editReply" in obj, get: obj => obj.editReply }, metadata: _metadata }, null, _instanceExtraInitializers);
187
+ __esDecorate(this, null, _markSynced_decorators, { kind: "method", name: "markSynced", static: false, private: false, access: { has: obj => "markSynced" in obj, get: obj => obj.markSynced }, metadata: _metadata }, null, _instanceExtraInitializers);
188
+ __esDecorate(this, null, _deleteDiscussion_decorators, { kind: "method", name: "deleteDiscussion", static: false, private: false, access: { has: obj => "deleteDiscussion" in obj, get: obj => obj.deleteDiscussion }, metadata: _metadata }, null, _instanceExtraInitializers);
189
+ __esDecorate(this, null, _mediaTranscript_decorators, { kind: "method", name: "mediaTranscript", static: false, private: false, access: { has: obj => "mediaTranscript" in obj, get: obj => obj.mediaTranscript }, metadata: _metadata }, null, _instanceExtraInitializers);
190
+ __esDecorate(this, null, _startLocalTranscript_decorators, { kind: "method", name: "startLocalTranscript", static: false, private: false, access: { has: obj => "startLocalTranscript" in obj, get: obj => obj.startLocalTranscript }, metadata: _metadata }, null, _instanceExtraInitializers);
191
+ __esDecorate(this, null, _localTranscript_decorators, { kind: "method", name: "localTranscript", static: false, private: false, access: { has: obj => "localTranscript" in obj, get: obj => obj.localTranscript }, metadata: _metadata }, null, _instanceExtraInitializers);
192
+ __esDecorate(this, null, _speakerNames_decorators, { kind: "method", name: "speakerNames", static: false, private: false, access: { has: obj => "speakerNames" in obj, get: obj => obj.speakerNames }, metadata: _metadata }, null, _instanceExtraInitializers);
193
+ __esDecorate(this, null, _nameSpeaker_decorators, { kind: "method", name: "nameSpeaker", static: false, private: false, access: { has: obj => "nameSpeaker" in obj, get: obj => obj.nameSpeaker }, metadata: _metadata }, null, _instanceExtraInitializers);
194
+ __esDecorate(this, null, _mediaStates_decorators, { kind: "method", name: "mediaStates", static: false, private: false, access: { has: obj => "mediaStates" in obj, get: obj => obj.mediaStates }, metadata: _metadata }, null, _instanceExtraInitializers);
195
+ __esDecorate(this, null, _cancelLocalTranscript_decorators, { kind: "method", name: "cancelLocalTranscript", static: false, private: false, access: { has: obj => "cancelLocalTranscript" in obj, get: obj => obj.cancelLocalTranscript }, metadata: _metadata }, null, _instanceExtraInitializers);
196
+ __esDecorate(this, null, _polish_decorators, { kind: "method", name: "polish", static: false, private: false, access: { has: obj => "polish" in obj, get: obj => obj.polish }, metadata: _metadata }, null, _instanceExtraInitializers);
197
+ __esDecorate(this, null, _setPolishOff_decorators, { kind: "method", name: "setPolishOff", static: false, private: false, access: { has: obj => "setPolishOff" in obj, get: obj => obj.setPolishOff }, metadata: _metadata }, null, _instanceExtraInitializers);
198
+ __esDecorate(this, null, _snapshotEntry_decorators, { kind: "method", name: "snapshotEntry", static: false, private: false, access: { has: obj => "snapshotEntry" in obj, get: obj => obj.snapshotEntry }, metadata: _metadata }, null, _instanceExtraInitializers);
139
199
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
140
200
  }
141
- static inject = ['reader'];
201
+ static inject = ['reader', 'readerNotes'];
142
202
  constructor(ctx, _config = {}) {
143
203
  super(ctx, 'readerRemote', { namespace: 'reader' });
144
204
  __runInitializers(this, _instanceExtraInitializers);
@@ -147,6 +207,10 @@ let ReaderRemoteService = (() => {
147
207
  get core() {
148
208
  return this.ctx.reader;
149
209
  }
210
+ /** The notes store this face delegates to. */
211
+ get notes() {
212
+ return this.ctx.readerNotes;
213
+ }
150
214
  /** Capability handshake: what the browser may rely on in this composition. */
151
215
  capabilities() {
152
216
  return this.core.capabilities();
@@ -280,6 +344,92 @@ let ReaderRemoteService = (() => {
280
344
  quoteToSideChat(request) {
281
345
  return this.core.quoteToSideChat(request);
282
346
  }
347
+ /* ------------------------------------------------------------------ notes */
348
+ /** One article's notes and the conversations that discussed it. */
349
+ async entryNotes(request) {
350
+ const entry = this.notes.entry(request.entryId);
351
+ return entry === undefined ? {} : { entry };
352
+ }
353
+ /** How many notes each article carries. */
354
+ async noteCounts() {
355
+ return { counts: this.notes.counts() };
356
+ }
357
+ /** Keep one excerpt (and its comment). */
358
+ addNote(request) {
359
+ return this.notes.addNote(request);
360
+ }
361
+ /** Change or clear one note's comment. */
362
+ updateNote(request) {
363
+ return this.notes.updateNote(request);
364
+ }
365
+ /** Delete one note. */
366
+ deleteNote(request) {
367
+ return this.notes.deleteNote(request);
368
+ }
369
+ /** Remember that a conversation discussed this article. */
370
+ recordDiscussion(request) {
371
+ return this.notes.recordSession(request);
372
+ }
373
+ /** Add the reader's own line under a note. */
374
+ addReply(request) {
375
+ return this.notes.addReply({ ...request, by: 'user' });
376
+ }
377
+ /** Rewrite (or, with empty text, remove) one line under a note. */
378
+ editReply(request) {
379
+ return this.notes.editReply(request);
380
+ }
381
+ /** Remember that a note or a discussion note became a canvas card. */
382
+ markSynced(request) {
383
+ return this.notes.markSynced(request);
384
+ }
385
+ /** Delete one discussion note. */
386
+ deleteDiscussion(request) {
387
+ return this.notes.deleteDiscussion(request);
388
+ }
389
+ /** A video entry's transcript from its platform captions (stored; `none` when it has none). */
390
+ mediaTranscript(request) {
391
+ return this.core.mediaTranscript(request);
392
+ }
393
+ /**
394
+ * Start (or rejoin) local transcription of an entry without captions.
395
+ * Answers `unavailable` without the transcription plugin and `needs-models`
396
+ * until the first model download is confirmed.
397
+ */
398
+ startLocalTranscript(request) {
399
+ return this.core.local.start(request);
400
+ }
401
+ /** Where an entry's local transcription stands, with the transcript so far. */
402
+ localTranscript(request) {
403
+ return Promise.resolve(this.core.local.status(request.entryId));
404
+ }
405
+ /** The reader's names for a transcript's speakers, by index. */
406
+ speakerNames(request) {
407
+ return Promise.resolve(this.core.speakerNames(request));
408
+ }
409
+ /** Name one speaker of a transcript (empty: back to 说话人 A…); answers all the names. */
410
+ nameSpeaker(request) {
411
+ return Promise.resolve(this.core.nameSpeaker(request));
412
+ }
413
+ /** The wall's video / audio cards: what each one's transcript is (captions, local, transcribing…). */
414
+ mediaStates(request) {
415
+ return Promise.resolve(this.core.mediaStates(request));
416
+ }
417
+ /** Leave (withdraws a queued job) or, with `force`, stop an entry's local transcription. */
418
+ cancelLocalTranscript(request) {
419
+ return Promise.resolve(this.core.local.cancel(request.entryId, request.force));
420
+ }
421
+ /** The article's 润色 layer (the agent's translation), when there is one for its current text. */
422
+ polish(request) {
423
+ return Promise.resolve(this.notes.polishView(request.entryId) ?? null);
424
+ }
425
+ /** Show one paragraph's machine translation instead of the 润色 (or back). */
426
+ setPolishOff(request) {
427
+ return Promise.resolve(this.notes.setPolishOff(request.entryId, request.paragraph, request.off) ?? null);
428
+ }
429
+ /** Hand the host the article's original text, block by block, for the model to read. */
430
+ snapshotEntry(request) {
431
+ return this.notes.snapshot(request);
432
+ }
283
433
  };
284
434
  })();
285
435
  export { ReaderRemoteService };
@@ -5,9 +5,12 @@
5
5
  *
6
6
  * Two seams shape the whole module:
7
7
  *
8
- * - **Fetch goes through `ctx.web.fetch`** — the host's sanctioned outbound
9
- * HTTP face, which brings public-address resolution, connection pinning, a
10
- * byte cap and a timeout. Its provider caps the decoded body at 100 000
8
+ * - **Fetch rides the official HTTP provider** — `ctx.web` is the permission
9
+ * (nothing is fetched without it), and its default `http` provider brings
10
+ * public-address resolution, connection pinning, a byte cap and a timeout.
11
+ * On the official web service the reader runs its own instance of that
12
+ * provider (`document-fetch.ts`), because the host's selected provider may
13
+ * answer Markdown instead of the document (seam registry S22). Its provider caps the decoded body at 100 000
11
14
  * characters by default, so a payload that comes back `truncated` is stored
12
15
  * as-is and FLAGGED; nothing here pretends it is a whole document. A
13
16
  * truncated feed cannot be parsed and a truncated page yields half an
@@ -35,7 +38,10 @@
35
38
  * @module @khorsheed/dsh-reader/service
36
39
  */
37
40
  import type { Context } from '@deepseek-ai/cordis';
38
- import { type ReaderAddFailure, type ReaderAddOutcome, type ReaderAddRefusal, type ReaderAnnotationOutcome, type ReaderBackfillCandidate, type ReaderEntryBodyView, type ReaderBody, type ReaderCapabilities, type ReaderMutationOutcome, type ReaderEntryFetchState, type ReaderEntryTranslationView, type ReaderPreviewFailure, type ReaderPreviewFailureCode, type ReaderImageResult, type ReaderRecentEntry, type ReaderRefreshResult, type ReaderSentenceLearn, type ReaderSourceSummary, type ReaderStorageStats, type ReaderTag } from './types.ts';
41
+ import { type ReaderAddFailure, type ReaderAddOutcome, type ReaderAddRefusal, type ReaderAnnotationOutcome, type ReaderBackfillCandidate, type ReaderEntryBodyView, type ReaderBody, type ReaderCapabilities, type ReaderMutationOutcome, type ReaderEntryFetchState, type ReaderEntryTranslationView, type ReaderPreviewFailure, type ReaderPreviewFailureCode, type ReaderImageResult, type ReaderRecentEntry, type ReaderRefreshResult, type ReaderSentenceLearn, type ReaderSourceSummary, type ReaderStorageStats, type ReaderTag, type MediaCardStates } from './types.ts';
42
+ import { type DocumentFetchLimits } from './document-fetch.ts';
43
+ import { LocalTranscriber } from './local-transcribe.ts';
44
+ import { ReaderMediaHost } from './media-host.ts';
39
45
  /**
40
46
  * How many entries one automatic backfill run may fetch.
41
47
  *
@@ -46,6 +52,15 @@ import { type ReaderAddFailure, type ReaderAddOutcome, type ReaderAddRefusal, ty
46
52
  export declare const BACKFILL_MAX_PER_RUN = 8;
47
53
  /** How long a FAILED fetch is left alone before it may be retried (6 hours). */
48
54
  export declare const BACKFILL_RETRY_MS: number;
55
+ /**
56
+ * How many sources one refresh fetches at once.
57
+ *
58
+ * A strictly sequential loop made one dead source (a full 30 s timeout) stall
59
+ * every source behind it — the "refresh takes minutes" half of the slow-load
60
+ * report. Three at a time is the politeness ceiling this package applies to
61
+ * other people's servers everywhere (the backfill runs at two).
62
+ */
63
+ export declare const REFRESH_CONCURRENCY = 3;
49
64
  /**
50
65
  * Thrown when the FETCH itself failed — a refused connection, a timeout, a
51
66
  * response over the seam's byte cap. Carries the seam's own message: the
@@ -73,6 +88,10 @@ export declare function classifyPayload(raw: string): 'feed' | 'page';
73
88
  /** The reader service. */
74
89
  export declare class ReaderService {
75
90
  private readonly store;
91
+ /** Video and audio entries: synthetic pages, channel feeds, transcripts. */
92
+ readonly media: ReaderMediaHost;
93
+ /** Local transcription of entries without captions (the optional `transcribe` engine). */
94
+ readonly local: LocalTranscriber;
76
95
  private readonly ctx;
77
96
  private timer;
78
97
  private disposed;
@@ -80,12 +99,16 @@ export declare class ReaderService {
80
99
  private memoryDoc;
81
100
  /** Rescued images, by URL — a pane remount re-fails every blocked image, and this is what keeps that a memory read. */
82
101
  private readonly imageCache;
102
+ /** Feeds and pages, as the origin served them (see `document-fetch.ts`). */
103
+ private readonly documents;
83
104
  /**
84
105
  * @param ctx - owning context.
85
106
  * @param config - optional state-root override.
86
107
  */
87
108
  constructor(ctx: Context, config?: {
88
109
  stateRoot?: string;
110
+ ytdlpPath?: string;
111
+ fetchLimits?: DocumentFetchLimits;
89
112
  });
90
113
  /** Capabilities the browser may rely on. */
91
114
  capabilities(): Promise<ReaderCapabilities>;
@@ -323,6 +346,8 @@ export declare class ReaderService {
323
346
  title?: string;
324
347
  /** A short excerpt (abstract / first paragraph), with the title. */
325
348
  excerpt?: string;
349
+ /** Keep regardless of the cache TTL (a transcript costs a yt-dlp run, not a page fetch). */
350
+ keep?: boolean;
326
351
  }): Promise<ReaderEntryBodyView>;
327
352
  /**
328
353
  * Which entries still need their full text, and which may be retried.
@@ -558,8 +583,6 @@ export declare class ReaderService {
558
583
  * known-unreadable verdict) still answers.
559
584
  */
560
585
  private resolvePaper;
561
- /** The probed web seam (no host import, no boot-time dependency). */
562
- private web;
563
586
  /** The probed side-chat service. */
564
587
  private sideChat;
565
588
  /**
@@ -591,6 +614,58 @@ export declare class ReaderService {
591
614
  * @returns the final URL and its decoded payload.
592
615
  */
593
616
  private fetchFollowing;
617
+ /**
618
+ * Fetch a source's payload, media-aware (media-host.ts).
619
+ *
620
+ * A YouTube channel is fetched as YouTube's own channel feed; a Xiaoyuzhou
621
+ * podcast page as the RSS document it is turned into; a single video or
622
+ * Xiaoyuzhou episode as a synthetic page carrying its media description.
623
+ * Everything else is the ordinary {@link fetchFollowing}.
624
+ *
625
+ * @param url - the source URL.
626
+ */
627
+ private fetchSource;
628
+ /**
629
+ * A video entry's transcript: the stored one, else the platform captions
630
+ * fetched now and kept (no TTL — it costs a yt-dlp run). A video known to
631
+ * have no captions answers so without running yt-dlp again.
632
+ *
633
+ * @param request - the entry and its video page URL.
634
+ * @returns the transcript markup, `none`, or why it failed.
635
+ */
636
+ mediaTranscript(request: {
637
+ entryId: string;
638
+ url: string;
639
+ }): Promise<{
640
+ entryId: string;
641
+ html?: string;
642
+ captions: 'platform' | 'none' | 'failed';
643
+ language?: string;
644
+ error?: string;
645
+ }>;
646
+ /** The reader's names for an entry's speakers, by index. */
647
+ speakerNames(request: {
648
+ entryId: string;
649
+ }): Record<string, string>;
650
+ /**
651
+ * Name one speaker of an entry's transcript (an empty name restores the default).
652
+ *
653
+ * @param request - the entry, the speaker's index, the name.
654
+ */
655
+ nameSpeaker(request: {
656
+ entryId: string;
657
+ speaker: number;
658
+ name: string;
659
+ }): Record<string, string>;
660
+ /**
661
+ * The wall's media cards: a live local job first, else what the captions
662
+ * record says (platform captions, a stored local transcript, none).
663
+ *
664
+ * @param request - the card ids on screen.
665
+ */
666
+ mediaStates(request: {
667
+ entryIds: readonly string[];
668
+ }): MediaCardStates;
594
669
  /** Arm (or re-arm) the daily refresh timer against the current document. */
595
670
  private armSchedule;
596
671
  }