@lingjingai/scriptctl 0.12.0 → 0.12.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.
@@ -49,7 +49,9 @@ export interface ProviderLike {
49
49
  extractAssetCuration?(sourceText: string, script: Dict): Promise<Dict> | Dict;
50
50
  extractEpisodeTitles?(sourceText: string, plan: Dict): Promise<Dict> | Dict;
51
51
  }
52
- export declare function enrichEpisodePlanTitles(sourceText: string, episodePlan: Dict, provider: ProviderLike): Promise<Dict>;
52
+ export declare const DEFAULT_TITLE_CHUNK_SIZE = 10;
53
+ export declare function chunkEpisodesNeedingTitles(episodePlan: Dict, chunkSize: number): Dict[][];
54
+ export declare function applyEpisodeTitlesToPlan(sourceText: string, episodePlan: Dict, generated: Map<number, string>): number[];
53
55
  export declare function formatBatchSource(sourceText: string, batchPlan: Dict): string;
54
56
  export declare function lineNumberAt(sourceText: string, offset: number): number;
55
57
  export declare function batchContextFor(sourceText: string, episode: Dict, start: number): Dict;
@@ -908,37 +908,48 @@ export function episodeTitleResponseMap(payload) {
908
908
  }
909
909
  return result;
910
910
  }
911
- export async function enrichEpisodePlanTitles(sourceText, episodePlan, provider) {
912
- const needsTitles = episodesNeedingGeneratedTitles(episodePlan);
913
- if (needsTitles.length === 0)
914
- return episodePlan;
915
- let payload;
916
- if (provider.extractEpisodeTitles) {
917
- payload = await provider.extractEpisodeTitles(sourceText, episodePlan);
918
- }
919
- else {
920
- payload = {
921
- episode_titles: needsTitles.map((ep) => ({
922
- episode: Number(ep["episode"] ?? 0),
923
- title: deterministicEpisodeShortTitle(sourceText, ep),
924
- })),
925
- };
926
- }
927
- const generated = episodeTitleResponseMap(payload);
928
- for (const episode of needsTitles) {
911
+ // Default episodes per title-generation request. Title planning is the only
912
+ // init step that would otherwise bundle EVERY episode's excerpt into a single
913
+ // LLM request — which makes a content filter reject the whole show at once.
914
+ // Chunking bounds the blast radius of any one poisoned excerpt.
915
+ export const DEFAULT_TITLE_CHUNK_SIZE = 10;
916
+ // Split the episodes that still need a generated title into bounded chunks,
917
+ // each suitable for an independent title-generation request.
918
+ export function chunkEpisodesNeedingTitles(episodePlan, chunkSize) {
919
+ const episodes = episodesNeedingGeneratedTitles(episodePlan);
920
+ const size = Math.max(1, Math.floor(chunkSize) || 1);
921
+ const chunks = [];
922
+ for (let i = 0; i < episodes.length; i += size) {
923
+ chunks.push(episodes.slice(i, i + size));
924
+ }
925
+ return chunks;
926
+ }
927
+ // Write generated titles back onto the plan in place. Any episode missing from
928
+ // `generated` (model omitted it, or its request was filtered/failed) degrades
929
+ // to a deterministic title rather than blocking the run. Returns the episode
930
+ // numbers that fell back so callers can report the degradation.
931
+ export function applyEpisodeTitlesToPlan(sourceText, episodePlan, generated) {
932
+ const degraded = [];
933
+ for (const episode of episodesNeedingGeneratedTitles(episodePlan)) {
929
934
  const episodeNum = Number(episode["episode"] ?? 0);
930
- let rawTitle = generated.get(episodeNum) || deterministicEpisodeShortTitle(sourceText, episode);
935
+ let rawTitle = generated.get(episodeNum);
936
+ let fromModel = Boolean(rawTitle);
937
+ if (!rawTitle)
938
+ rawTitle = deterministicEpisodeShortTitle(sourceText, episode);
931
939
  let title = formatEpisodeTitle(episodeNum, rawTitle);
932
940
  if (!title) {
941
+ fromModel = false;
933
942
  rawTitle = deterministicEpisodeShortTitle(sourceText, episode);
934
943
  title = formatEpisodeTitle(episodeNum, rawTitle) || `第${episodeNum}集:剧情转折${episodeNum}`;
935
944
  }
936
945
  episode["generated_title"] = rawTitle;
937
946
  episode["title"] = title;
938
947
  episode["title_status"] = "generated";
939
- episode["title_source"] = "generated";
948
+ episode["title_source"] = fromModel ? "generated" : "deterministic";
949
+ if (!fromModel)
950
+ degraded.push(episodeNum);
940
951
  }
941
- return episodePlan;
952
+ return degraded;
942
953
  }
943
954
  // ---------------------------------------------------------------------------
944
955
  // Batch source formatting and planning