@khorsheed/dsh-reader 0.3.0 → 0.3.1

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 (87) hide show
  1. package/CHANGELOG.md +22 -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 +29054 -6698
  7. package/lib/index.js +3416 -334
  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 +501 -28
  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/index.d.ts +6 -0
  54. package/lib/types/index.js +3 -0
  55. package/lib/types/local-transcribe.d.ts +171 -0
  56. package/lib/types/local-transcribe.js +473 -0
  57. package/lib/types/media-host.d.ts +91 -0
  58. package/lib/types/media-host.js +230 -0
  59. package/lib/types/media.d.ts +100 -0
  60. package/lib/types/media.js +207 -0
  61. package/lib/types/notes-types.d.ts +158 -0
  62. package/lib/types/notes-types.js +8 -0
  63. package/lib/types/notes.d.ts +213 -0
  64. package/lib/types/notes.js +718 -0
  65. package/lib/types/polish.d.ts +41 -0
  66. package/lib/types/polish.js +136 -0
  67. package/lib/types/remote.d.ts +118 -0
  68. package/lib/types/remote.js +151 -1
  69. package/lib/types/service.d.ts +71 -1
  70. package/lib/types/service.js +280 -59
  71. package/lib/types/skill.d.ts +20 -0
  72. package/lib/types/skill.js +78 -0
  73. package/lib/types/store.js +30 -8
  74. package/lib/types/tool.d.ts +23 -0
  75. package/lib/types/tool.js +40 -0
  76. package/lib/types/tools.d.ts +69 -0
  77. package/lib/types/tools.js +400 -0
  78. package/lib/types/types.d.ts +87 -0
  79. package/lib/types/types.js +66 -0
  80. package/lib/types/xiaoyuzhou.d.ts +43 -0
  81. package/lib/types/xiaoyuzhou.js +134 -0
  82. package/lib/types/ytdlp.d.ts +111 -0
  83. package/lib/types/ytdlp.js +269 -0
  84. package/package.json +29 -20
  85. package/lib/invariant.js +0 -38
  86. package/lib/types/invariant.d.ts +0 -29
  87. 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 };
@@ -35,7 +35,9 @@
35
35
  * @module @khorsheed/dsh-reader/service
36
36
  */
37
37
  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';
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, type MediaCardStates } from './types.ts';
39
+ import { LocalTranscriber } from './local-transcribe.ts';
40
+ import { ReaderMediaHost } from './media-host.ts';
39
41
  /**
40
42
  * How many entries one automatic backfill run may fetch.
41
43
  *
@@ -46,6 +48,15 @@ import { type ReaderAddFailure, type ReaderAddOutcome, type ReaderAddRefusal, ty
46
48
  export declare const BACKFILL_MAX_PER_RUN = 8;
47
49
  /** How long a FAILED fetch is left alone before it may be retried (6 hours). */
48
50
  export declare const BACKFILL_RETRY_MS: number;
51
+ /**
52
+ * How many sources one refresh fetches at once.
53
+ *
54
+ * A strictly sequential loop made one dead source (a full 30 s timeout) stall
55
+ * every source behind it — the "refresh takes minutes" half of the slow-load
56
+ * report. Three at a time is the politeness ceiling this package applies to
57
+ * other people's servers everywhere (the backfill runs at two).
58
+ */
59
+ export declare const REFRESH_CONCURRENCY = 3;
49
60
  /**
50
61
  * Thrown when the FETCH itself failed — a refused connection, a timeout, a
51
62
  * response over the seam's byte cap. Carries the seam's own message: the
@@ -73,6 +84,10 @@ export declare function classifyPayload(raw: string): 'feed' | 'page';
73
84
  /** The reader service. */
74
85
  export declare class ReaderService {
75
86
  private readonly store;
87
+ /** Video and audio entries: synthetic pages, channel feeds, transcripts. */
88
+ readonly media: ReaderMediaHost;
89
+ /** Local transcription of entries without captions (the optional `transcribe` engine). */
90
+ readonly local: LocalTranscriber;
76
91
  private readonly ctx;
77
92
  private timer;
78
93
  private disposed;
@@ -86,6 +101,7 @@ export declare class ReaderService {
86
101
  */
87
102
  constructor(ctx: Context, config?: {
88
103
  stateRoot?: string;
104
+ ytdlpPath?: string;
89
105
  });
90
106
  /** Capabilities the browser may rely on. */
91
107
  capabilities(): Promise<ReaderCapabilities>;
@@ -323,6 +339,8 @@ export declare class ReaderService {
323
339
  title?: string;
324
340
  /** A short excerpt (abstract / first paragraph), with the title. */
325
341
  excerpt?: string;
342
+ /** Keep regardless of the cache TTL (a transcript costs a yt-dlp run, not a page fetch). */
343
+ keep?: boolean;
326
344
  }): Promise<ReaderEntryBodyView>;
327
345
  /**
328
346
  * Which entries still need their full text, and which may be retried.
@@ -591,6 +609,58 @@ export declare class ReaderService {
591
609
  * @returns the final URL and its decoded payload.
592
610
  */
593
611
  private fetchFollowing;
612
+ /**
613
+ * Fetch a source's payload, media-aware (media-host.ts).
614
+ *
615
+ * A YouTube channel is fetched as YouTube's own channel feed; a Xiaoyuzhou
616
+ * podcast page as the RSS document it is turned into; a single video or
617
+ * Xiaoyuzhou episode as a synthetic page carrying its media description.
618
+ * Everything else is the ordinary {@link fetchFollowing}.
619
+ *
620
+ * @param url - the source URL.
621
+ */
622
+ private fetchSource;
623
+ /**
624
+ * A video entry's transcript: the stored one, else the platform captions
625
+ * fetched now and kept (no TTL — it costs a yt-dlp run). A video known to
626
+ * have no captions answers so without running yt-dlp again.
627
+ *
628
+ * @param request - the entry and its video page URL.
629
+ * @returns the transcript markup, `none`, or why it failed.
630
+ */
631
+ mediaTranscript(request: {
632
+ entryId: string;
633
+ url: string;
634
+ }): Promise<{
635
+ entryId: string;
636
+ html?: string;
637
+ captions: 'platform' | 'none' | 'failed';
638
+ language?: string;
639
+ error?: string;
640
+ }>;
641
+ /** The reader's names for an entry's speakers, by index. */
642
+ speakerNames(request: {
643
+ entryId: string;
644
+ }): Record<string, string>;
645
+ /**
646
+ * Name one speaker of an entry's transcript (an empty name restores the default).
647
+ *
648
+ * @param request - the entry, the speaker's index, the name.
649
+ */
650
+ nameSpeaker(request: {
651
+ entryId: string;
652
+ speaker: number;
653
+ name: string;
654
+ }): Record<string, string>;
655
+ /**
656
+ * The wall's media cards: a live local job first, else what the captions
657
+ * record says (platform captions, a stored local transcript, none).
658
+ *
659
+ * @param request - the card ids on screen.
660
+ */
661
+ mediaStates(request: {
662
+ entryIds: readonly string[];
663
+ }): MediaCardStates;
594
664
  /** Arm (or re-arm) the daily refresh timer against the current document. */
595
665
  private armSchedule;
596
666
  }