@rtsee/messenger-signaling 0.0.122 → 0.0.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/messenger-signaling/src/models/RTSeeMessengerSignaling.d.ts.map +1 -1
- package/dist/messenger-signaling/src/models/RTSeeMessengerSignaling.js +17 -9
- package/dist/messenger-signaling/src/models/RTSeeMessengerSignaling.js.map +1 -1
- package/dist/messenger-signaling/src/models/ozzy/connectors/GCloudConverseConnector.d.ts +7 -0
- package/dist/messenger-signaling/src/models/ozzy/connectors/GCloudConverseConnector.d.ts.map +1 -1
- package/dist/messenger-signaling/src/models/ozzy/connectors/GCloudConverseConnector.js +83 -14
- package/dist/messenger-signaling/src/models/ozzy/connectors/GCloudConverseConnector.js.map +1 -1
- package/dist/messenger-signaling/src/models/ozzy/tasks/StoryRepairTask.d.ts +25 -0
- package/dist/messenger-signaling/src/models/ozzy/tasks/StoryRepairTask.d.ts.map +1 -0
- package/dist/messenger-signaling/src/models/ozzy/tasks/StoryRepairTask.js +673 -0
- package/dist/messenger-signaling/src/models/ozzy/tasks/StoryRepairTask.js.map +1 -0
- package/dist/messenger-signaling/src/models/ozzy/tasks/StoryTranslateInPlaceTask.d.ts.map +1 -1
- package/dist/messenger-signaling/src/models/ozzy/tasks/StoryTranslateInPlaceTask.js +25 -7
- package/dist/messenger-signaling/src/models/ozzy/tasks/StoryTranslateInPlaceTask.js.map +1 -1
- package/dist/messenger-signaling/src/models/ozzy/tasks/index.d.ts +1 -0
- package/dist/messenger-signaling/src/models/ozzy/tasks/index.d.ts.map +1 -1
- package/dist/messenger-signaling/src/models/ozzy/tasks/index.js +1 -0
- package/dist/messenger-signaling/src/models/ozzy/tasks/index.js.map +1 -1
- package/dist/messenger-signaling/src/models/ozzy/workflows/index.d.ts +1 -0
- package/dist/messenger-signaling/src/models/ozzy/workflows/index.d.ts.map +1 -1
- package/dist/messenger-signaling/src/models/ozzy/workflows/index.js +1 -0
- package/dist/messenger-signaling/src/models/ozzy/workflows/index.js.map +1 -1
- package/dist/messenger-signaling/src/models/ozzy/workflows/storyRepair.d.ts +9 -0
- package/dist/messenger-signaling/src/models/ozzy/workflows/storyRepair.d.ts.map +1 -0
- package/dist/messenger-signaling/src/models/ozzy/workflows/storyRepair.js +36 -0
- package/dist/messenger-signaling/src/models/ozzy/workflows/storyRepair.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StoryRepairTask = void 0;
|
|
4
|
+
exports.getStoryRepairTask = getStoryRepairTask;
|
|
5
|
+
const common_1 = require("@rtsee/common");
|
|
6
|
+
const Task_js_1 = require("./Task.js");
|
|
7
|
+
const MAX_SLIDES_PER_STORY = 500;
|
|
8
|
+
const DEFAULT_IMAGE_URL = 'https://rtsee-files-vault-public.s3.us-east-1.amazonaws.com/default/default-story-pic.png';
|
|
9
|
+
function buildRepairSystemPrompt(options) {
|
|
10
|
+
const sections = [
|
|
11
|
+
`You are an expert story editor for the RTSee presentation player.`,
|
|
12
|
+
`Your task is to inspect an existing story, its sequenced slides, and its existing story elements (characters, locations, artifacts), and repair/enrich them strictly according to the enabled repair options below.`,
|
|
13
|
+
`The output descriptions you provide must be SEO friendly.`,
|
|
14
|
+
];
|
|
15
|
+
if (options.grammarAndFlow) {
|
|
16
|
+
sections.push(`
|
|
17
|
+
1. GRAMMAR, READABILITY & FLOW REPAIR (ENABLED):
|
|
18
|
+
- The story text may have been copied from old book scans or rough drafts and might contain typos, grammar mistakes, awkward phrasing, or punctuation errors. Fix all of these and improve readability and narrative flow across:
|
|
19
|
+
* "story": "description" (1-2 sentence SEO-friendly teaser), "history" (5-6 sentences on the origins/history of the story), and "moralMeaning" (5-6 sentences on what can be learned from the story). If "history" or "moralMeaning" are missing or empty, write them.
|
|
20
|
+
* "slides": fix grammar, punctuation, typos, and improve flow of each slide's "text" and "illustrationDescription".
|
|
21
|
+
* "updatedLocations", "updatedCharacters", "updatedArtifacts": if any existing elements have typos, weak descriptions, or missing/weak "visualDescription" prompts, include their updated entries (keeping their exact existing "id") in these arrays.
|
|
22
|
+
- CRITICAL ZERO-LOSS TEXT INVARIANT:
|
|
23
|
+
* You must keep slide "text" very close to the original story text.
|
|
24
|
+
* DO NOT summarize, shorten, or condense any slide text.
|
|
25
|
+
* Do NOT drop, merge, or reorder slides — keep the exact same slide "id" values.
|
|
26
|
+
* Use line-by-line dialogue style. Every time the active speaker shifts, start a fresh paragraph.
|
|
27
|
+
- CRITICAL SLIDE ILLUSTRATION PROMPT ("illustrationDescription"):
|
|
28
|
+
* Ensure EVERY slide has a vivid, detailed, self-contained "illustrationDescription" field used directly as the generative prompt for our image generator (NanoBanana) to produce the fairy tale illustration for that slide:
|
|
29
|
+
- Specify what is visually happening in the foreground, midground, and background.
|
|
30
|
+
- Explicitly mention which characters are present, their actions, facial expressions, poses, clothing, and interactions.
|
|
31
|
+
- Include the environment/setting (lighting, atmospheric mood, weather, scenery matching the slide's location).
|
|
32
|
+
- Include any prominent artifacts held, used, or visible in the scene.
|
|
33
|
+
- Use painterly, cinematic visual descriptors suited for enchanted storybook illustrations.
|
|
34
|
+
- Do NOT write meta text such as "In this slide" or "This image illustrates". Write directly as an image prompt.`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
sections.push(`
|
|
38
|
+
1. GRAMMAR, READABILITY & FLOW REPAIR (DISABLED):
|
|
39
|
+
- Do NOT alter the existing story or slide texts. Omit "story", "updatedLocations", "updatedCharacters", and "updatedArtifacts", and keep slide "text" and "illustrationDescription" unchanged.`);
|
|
40
|
+
}
|
|
41
|
+
const enabledEntities = [];
|
|
42
|
+
if (options.locations) {
|
|
43
|
+
enabledEntities.push(`- LOCATIONS (ENABLED): Check "existingLocations" against the story and slides. Identify any important places where episodes of the story take place that are MISSING from "existingLocations". Return only genuinely missing important locations in "newLocations" (each with "id" like "new-loc-1", "name": 2-3 words, "description": 1-2 sentences on its story significance, "visualDescription": detailed visual prompt for NanoBanana image generation). If all important locations are already present in "existingLocations", return [] for "newLocations". Link both existing location IDs and new location IDs ("new-loc-1") to each slide's "location" field.`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
enabledEntities.push(`- LOCATIONS (DISABLED): Return [] for "newLocations" and keep each slide's existing "location" unchanged.`);
|
|
47
|
+
}
|
|
48
|
+
if (options.characters) {
|
|
49
|
+
enabledEntities.push(`- CHARACTERS (ENABLED): Check "existingCharacters" against the story and slides. Identify any important heroes, villains, or supporting characters in the story that are MISSING from "existingCharacters". Return only genuinely missing important characters in "newCharacters" (each with "id" like "new-char-1", "name", "description": 1-2 sentences, "visualDescription": detailed visual appearance for NanoBanana image generation). If all important characters are already present in "existingCharacters", return [] for "newCharacters". Link both existing character IDs and new character IDs ("new-char-1") to each slide's "characters" array.`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
enabledEntities.push(`- CHARACTERS (DISABLED): Return [] for "newCharacters" and keep each slide's existing "characters" array unchanged.`);
|
|
53
|
+
}
|
|
54
|
+
if (options.artifacts) {
|
|
55
|
+
enabledEntities.push(`- ARTIFACTS (ENABLED): Check "existingArtifacts" against the story and slides. Identify any meaningful or memorable objects from the story that are MISSING from "existingArtifacts". Return only genuinely missing important artifacts in "newArtifacts" (each with "id" like "new-art-1", "name", "description": 1-2 sentences, "visualDescription": detailed visual appearance for NanoBanana image generation). If all important artifacts are already present in "existingArtifacts", return [] for "newArtifacts". Link both existing artifact IDs and new artifact IDs ("new-art-1") to each slide's "artifacts" array.`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
enabledEntities.push(`- ARTIFACTS (DISABLED): Return [] for "newArtifacts" and keep each slide's existing "artifacts" array unchanged.`);
|
|
59
|
+
}
|
|
60
|
+
sections.push(`\n2. ENTITY DISCOVERY & LINKING RULES:\n${enabledEntities.join('\n')}`);
|
|
61
|
+
sections.push(`
|
|
62
|
+
Respond with ONLY a valid JSON object, no markdown fences, no comments, using this exact shape:
|
|
63
|
+
{
|
|
64
|
+
"story": {
|
|
65
|
+
"description": string,
|
|
66
|
+
"history": string,
|
|
67
|
+
"moralMeaning": string
|
|
68
|
+
},
|
|
69
|
+
"newLocations": [{"id": string, "name": string, "description": string, "visualDescription": string}],
|
|
70
|
+
"newCharacters": [{"id": string, "name": string, "description": string, "visualDescription": string}],
|
|
71
|
+
"newArtifacts": [{"id": string, "name": string, "description": string, "visualDescription": string}],
|
|
72
|
+
"updatedLocations": [{"id": string, "name": string, "description": string, "visualDescription": string}],
|
|
73
|
+
"updatedCharacters": [{"id": string, "name": string, "description": string, "visualDescription": string}],
|
|
74
|
+
"updatedArtifacts": [{"id": string, "name": string, "description": string, "visualDescription": string}],
|
|
75
|
+
"slides": [
|
|
76
|
+
{
|
|
77
|
+
"id": string,
|
|
78
|
+
"text": string,
|
|
79
|
+
"illustrationDescription": string,
|
|
80
|
+
"location": string | null,
|
|
81
|
+
"characters": [string],
|
|
82
|
+
"artifacts": [string]
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}`);
|
|
86
|
+
return sections.join('\n');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Repairs an existing story: fixes grammar/flow of story, slides, and entities,
|
|
90
|
+
* and/or identifies and creates missing important characters, locations, and artifacts
|
|
91
|
+
* and links them to slides.
|
|
92
|
+
*/
|
|
93
|
+
class StoryRepairTask extends Task_js_1.OzzyTask {
|
|
94
|
+
presentation;
|
|
95
|
+
constructor(options, presentation) {
|
|
96
|
+
super(options);
|
|
97
|
+
this.presentation = presentation;
|
|
98
|
+
}
|
|
99
|
+
async execute() {
|
|
100
|
+
const repairOptions = this.validateInput();
|
|
101
|
+
const needsLlmRepair = repairOptions.grammarAndFlow ||
|
|
102
|
+
repairOptions.characters ||
|
|
103
|
+
repairOptions.artifacts ||
|
|
104
|
+
repairOptions.locations;
|
|
105
|
+
const agent = needsLlmRepair ? await this.resolveAgent() : null;
|
|
106
|
+
this.log(`Loading story "${this.input.storyName || this.input.storyId}"...`);
|
|
107
|
+
const story = await this.presentation.getStory(this.transport, {
|
|
108
|
+
storyId: this.input.storyId,
|
|
109
|
+
});
|
|
110
|
+
if (!story) {
|
|
111
|
+
throw new Error(`Story ${this.input.storyId} not found.`);
|
|
112
|
+
}
|
|
113
|
+
this.log('Loading slides and existing story elements...');
|
|
114
|
+
const [slides, charRes, locRes, artRes] = await Promise.all([
|
|
115
|
+
this.loadSlides(story.id),
|
|
116
|
+
this.presentation
|
|
117
|
+
.getCharacters(this.transport, { storyId: story.id, limit: 100 })
|
|
118
|
+
.catch(() => ({
|
|
119
|
+
characters: [],
|
|
120
|
+
total: 0,
|
|
121
|
+
})),
|
|
122
|
+
this.presentation
|
|
123
|
+
.getLocations(this.transport, { storyId: story.id, limit: 100 })
|
|
124
|
+
.catch(() => ({
|
|
125
|
+
locations: [],
|
|
126
|
+
total: 0,
|
|
127
|
+
})),
|
|
128
|
+
this.presentation
|
|
129
|
+
.getArtifacts(this.transport, { storyId: story.id, limit: 100 })
|
|
130
|
+
.catch(() => ({
|
|
131
|
+
artifacts: [],
|
|
132
|
+
total: 0,
|
|
133
|
+
})),
|
|
134
|
+
]);
|
|
135
|
+
const existingCharacters = charRes?.characters || [];
|
|
136
|
+
const existingLocations = locRes?.locations || [];
|
|
137
|
+
const existingArtifacts = artRes?.artifacts || [];
|
|
138
|
+
this.log(`Loaded ${slides.length} slide(s), ${existingCharacters.length} character(s), ` +
|
|
139
|
+
`${existingLocations.length} location(s), ${existingArtifacts.length} artifact(s).`);
|
|
140
|
+
let addedLocationsIds = [];
|
|
141
|
+
let addedCharactersIds = [];
|
|
142
|
+
let addedArtifactsIds = [];
|
|
143
|
+
let updatedSlidesCount = 0;
|
|
144
|
+
let updatedStoryDescription = story.description;
|
|
145
|
+
if (needsLlmRepair && agent) {
|
|
146
|
+
const model = agent.agentConfiguration.model?.model;
|
|
147
|
+
const activeFlags = [
|
|
148
|
+
repairOptions.grammarAndFlow ? 'Grammar & Flow' : '',
|
|
149
|
+
repairOptions.characters ? 'Characters' : '',
|
|
150
|
+
repairOptions.artifacts ? 'Artifacts' : '',
|
|
151
|
+
repairOptions.locations ? 'Locations' : '',
|
|
152
|
+
].filter(Boolean);
|
|
153
|
+
this.log(`Asking ${agent.name || agent.agentConfiguration.name} (${model || 'default model'}) ` +
|
|
154
|
+
`to repair story (${activeFlags.join(', ')})...`);
|
|
155
|
+
const prompt = this.buildPrompt(story, slides, existingLocations, existingCharacters, existingArtifacts, repairOptions);
|
|
156
|
+
const { text, cost } = await this.context.ozzy.complete({
|
|
157
|
+
prompt,
|
|
158
|
+
systemPrompt: `${agent.agentConfiguration.systemPrompt || ''}\n${buildRepairSystemPrompt(repairOptions)}`,
|
|
159
|
+
transport: this.transport,
|
|
160
|
+
model,
|
|
161
|
+
agentId: agent.id,
|
|
162
|
+
maxTokens: 24000,
|
|
163
|
+
onChunk: (chunk) => this.log(chunk, 'chunk'),
|
|
164
|
+
});
|
|
165
|
+
if (cost) {
|
|
166
|
+
const fmt = (n) => (n || 0).toLocaleString('en-US');
|
|
167
|
+
this.log(`Token usage: ${fmt(cost.inputTokens)} in / ${fmt(cost.outputTokens)} out / ${fmt(cost.totalTokens)} total`, 'info', cost);
|
|
168
|
+
}
|
|
169
|
+
this.log('Response received, parsing repair instructions...');
|
|
170
|
+
const draft = this.parseRepairDraft(text);
|
|
171
|
+
// Pre-populate entityIdMap with all existing entities (by id and lowercase name)
|
|
172
|
+
const entityIdMap = new Map();
|
|
173
|
+
const existingLocNames = new Set();
|
|
174
|
+
const existingCharNames = new Set();
|
|
175
|
+
const existingArtNames = new Set();
|
|
176
|
+
for (const loc of existingLocations) {
|
|
177
|
+
entityIdMap.set(loc.id, loc.id);
|
|
178
|
+
if (loc.name) {
|
|
179
|
+
const lower = loc.name.trim().toLowerCase();
|
|
180
|
+
entityIdMap.set(lower, loc.id);
|
|
181
|
+
existingLocNames.add(lower);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
for (const char of existingCharacters) {
|
|
185
|
+
entityIdMap.set(char.id, char.id);
|
|
186
|
+
if (char.name) {
|
|
187
|
+
const lower = char.name.trim().toLowerCase();
|
|
188
|
+
entityIdMap.set(lower, char.id);
|
|
189
|
+
existingCharNames.add(lower);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const art of existingArtifacts) {
|
|
193
|
+
entityIdMap.set(art.id, art.id);
|
|
194
|
+
if (art.name) {
|
|
195
|
+
const lower = art.name.trim().toLowerCase();
|
|
196
|
+
entityIdMap.set(lower, art.id);
|
|
197
|
+
existingArtNames.add(lower);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// 1. Create missing entities for enabled categories
|
|
201
|
+
if (repairOptions.locations && draft.newLocations.length > 0) {
|
|
202
|
+
const toCreate = draft.newLocations.filter((l) => {
|
|
203
|
+
const lower = l.name.toLowerCase();
|
|
204
|
+
if (existingLocNames.has(lower)) {
|
|
205
|
+
if (l.id && entityIdMap.get(lower)) {
|
|
206
|
+
entityIdMap.set(l.id, entityIdMap.get(lower));
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
});
|
|
212
|
+
addedLocationsIds = await this.createElements('location', toCreate, DEFAULT_IMAGE_URL, entityIdMap);
|
|
213
|
+
}
|
|
214
|
+
if (repairOptions.characters && draft.newCharacters.length > 0) {
|
|
215
|
+
const toCreate = draft.newCharacters.filter((c) => {
|
|
216
|
+
const lower = c.name.toLowerCase();
|
|
217
|
+
if (existingCharNames.has(lower)) {
|
|
218
|
+
if (c.id && entityIdMap.get(lower)) {
|
|
219
|
+
entityIdMap.set(c.id, entityIdMap.get(lower));
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
});
|
|
225
|
+
addedCharactersIds = await this.createElements('character', toCreate, DEFAULT_IMAGE_URL, entityIdMap);
|
|
226
|
+
}
|
|
227
|
+
if (repairOptions.artifacts && draft.newArtifacts.length > 0) {
|
|
228
|
+
const toCreate = draft.newArtifacts.filter((a) => {
|
|
229
|
+
const lower = a.name.toLowerCase();
|
|
230
|
+
if (existingArtNames.has(lower)) {
|
|
231
|
+
if (a.id && entityIdMap.get(lower)) {
|
|
232
|
+
entityIdMap.set(a.id, entityIdMap.get(lower));
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
return true;
|
|
237
|
+
});
|
|
238
|
+
addedArtifactsIds = await this.createElements('artifact', toCreate, DEFAULT_IMAGE_URL, entityIdMap);
|
|
239
|
+
}
|
|
240
|
+
// 2. Update existing entities if grammarAndFlow is enabled
|
|
241
|
+
if (repairOptions.grammarAndFlow) {
|
|
242
|
+
await this.updateExistingElements('location', existingLocations, draft.updatedLocations);
|
|
243
|
+
await this.updateExistingElements('character', existingCharacters, draft.updatedCharacters);
|
|
244
|
+
await this.updateExistingElements('artifact', existingArtifacts, draft.updatedArtifacts);
|
|
245
|
+
}
|
|
246
|
+
// 3. Update Story metadata and entity ID lists
|
|
247
|
+
const mergedLocationIds = Array.from(new Set([
|
|
248
|
+
...(story.locationIds || []),
|
|
249
|
+
...existingLocations.map((l) => l.id),
|
|
250
|
+
...addedLocationsIds,
|
|
251
|
+
]));
|
|
252
|
+
const mergedCharactersIds = Array.from(new Set([
|
|
253
|
+
...(story.charactersIds || []),
|
|
254
|
+
...existingCharacters.map((c) => c.id),
|
|
255
|
+
...addedCharactersIds,
|
|
256
|
+
]));
|
|
257
|
+
const mergedArtifactsIds = Array.from(new Set([
|
|
258
|
+
...(story.artifactsIds || []),
|
|
259
|
+
...existingArtifacts.map((a) => a.id),
|
|
260
|
+
...addedArtifactsIds,
|
|
261
|
+
]));
|
|
262
|
+
updatedStoryDescription =
|
|
263
|
+
repairOptions.grammarAndFlow && draft.story?.description?.trim()
|
|
264
|
+
? draft.story.description.trim()
|
|
265
|
+
: story.description;
|
|
266
|
+
const updatedStoryHistory = repairOptions.grammarAndFlow && draft.story?.history?.trim()
|
|
267
|
+
? draft.story.history.trim()
|
|
268
|
+
: story.history;
|
|
269
|
+
const updatedStoryMoralMeaning = repairOptions.grammarAndFlow && draft.story?.moralMeaning?.trim()
|
|
270
|
+
? draft.story.moralMeaning.trim()
|
|
271
|
+
: story.moralMeaning;
|
|
272
|
+
const storyChanged = updatedStoryDescription !== story.description ||
|
|
273
|
+
updatedStoryHistory !== story.history ||
|
|
274
|
+
updatedStoryMoralMeaning !== story.moralMeaning ||
|
|
275
|
+
addedLocationsIds.length > 0 ||
|
|
276
|
+
addedCharactersIds.length > 0 ||
|
|
277
|
+
addedArtifactsIds.length > 0 ||
|
|
278
|
+
mergedLocationIds.length !== (story.locationIds || []).length ||
|
|
279
|
+
mergedCharactersIds.length !== (story.charactersIds || []).length ||
|
|
280
|
+
mergedArtifactsIds.length !== (story.artifactsIds || []).length;
|
|
281
|
+
if (storyChanged) {
|
|
282
|
+
this.log('Updating story details and linked elements...');
|
|
283
|
+
await this.presentation.editStory(this.transport, {
|
|
284
|
+
...story,
|
|
285
|
+
description: updatedStoryDescription,
|
|
286
|
+
history: updatedStoryHistory,
|
|
287
|
+
moralMeaning: updatedStoryMoralMeaning,
|
|
288
|
+
locationIds: mergedLocationIds,
|
|
289
|
+
charactersIds: mergedCharactersIds,
|
|
290
|
+
artifactsIds: mergedArtifactsIds,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
// 4. Update slides
|
|
294
|
+
const draftSlidesById = new Map();
|
|
295
|
+
for (const ds of draft.slides) {
|
|
296
|
+
if (ds.id) {
|
|
297
|
+
draftSlidesById.set(ds.id, ds);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
for (let i = 0; i < slides.length; i++) {
|
|
301
|
+
const slide = slides[i];
|
|
302
|
+
const draftSlide = draftSlidesById.get(slide.id) || draft.slides[i];
|
|
303
|
+
if (!draftSlide) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const nextText = repairOptions.grammarAndFlow && draftSlide.text?.trim()
|
|
307
|
+
? draftSlide.text.trim()
|
|
308
|
+
: slide.text;
|
|
309
|
+
const nextIllustrationDescription = repairOptions.grammarAndFlow && draftSlide.illustrationDescription?.trim()
|
|
310
|
+
? draftSlide.illustrationDescription.trim()
|
|
311
|
+
: slide.illustrationDescription;
|
|
312
|
+
let nextLocationId = slide.locationId;
|
|
313
|
+
if (repairOptions.locations && draftSlide.location !== undefined) {
|
|
314
|
+
if (draftSlide.location === null || draftSlide.location === '') {
|
|
315
|
+
nextLocationId = slide.locationId;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const resolvedLoc = entityIdMap.get(draftSlide.location) ||
|
|
319
|
+
entityIdMap.get(draftSlide.location.toLowerCase());
|
|
320
|
+
if (resolvedLoc) {
|
|
321
|
+
nextLocationId = resolvedLoc;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
let nextCharactersIds = slide.charactersIds || [];
|
|
326
|
+
if (repairOptions.characters && Array.isArray(draftSlide.characters)) {
|
|
327
|
+
const resolvedChars = draftSlide.characters
|
|
328
|
+
.map((ref) => entityIdMap.get(ref) || entityIdMap.get(ref.toLowerCase()))
|
|
329
|
+
.filter((id) => Boolean(id));
|
|
330
|
+
nextCharactersIds = Array.from(new Set([...(slide.charactersIds || []), ...resolvedChars]));
|
|
331
|
+
}
|
|
332
|
+
let nextArtifactsIds = slide.artifactsIds || [];
|
|
333
|
+
if (repairOptions.artifacts && Array.isArray(draftSlide.artifacts)) {
|
|
334
|
+
const resolvedArts = draftSlide.artifacts
|
|
335
|
+
.map((ref) => entityIdMap.get(ref) || entityIdMap.get(ref.toLowerCase()))
|
|
336
|
+
.filter((id) => Boolean(id));
|
|
337
|
+
nextArtifactsIds = Array.from(new Set([...(slide.artifactsIds || []), ...resolvedArts]));
|
|
338
|
+
}
|
|
339
|
+
const slideChanged = nextText !== slide.text ||
|
|
340
|
+
nextIllustrationDescription !== slide.illustrationDescription ||
|
|
341
|
+
nextLocationId !== slide.locationId ||
|
|
342
|
+
!this.sameStringArray(nextCharactersIds, slide.charactersIds || []) ||
|
|
343
|
+
!this.sameStringArray(nextArtifactsIds, slide.artifactsIds || []);
|
|
344
|
+
if (slideChanged) {
|
|
345
|
+
try {
|
|
346
|
+
await this.presentation.editSlide(this.transport, {
|
|
347
|
+
...slide,
|
|
348
|
+
text: nextText,
|
|
349
|
+
illustrationDescription: nextIllustrationDescription,
|
|
350
|
+
locationId: nextLocationId,
|
|
351
|
+
charactersIds: nextCharactersIds,
|
|
352
|
+
artifactsIds: nextArtifactsIds,
|
|
353
|
+
});
|
|
354
|
+
updatedSlidesCount++;
|
|
355
|
+
this.log(`Slide ${i + 1}/${slides.length} updated.`);
|
|
356
|
+
}
|
|
357
|
+
catch (e) {
|
|
358
|
+
this.log(`Failed to update slide ${i + 1}: ${e?.message || e}`, 'warn');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// 5. Sanitize & upload story-related images to the public S3 bucket when enabled
|
|
364
|
+
let sanitizedImagesCount = 0;
|
|
365
|
+
if (repairOptions.sanitizeImages) {
|
|
366
|
+
if (this.presentation.sanitizeStoryImages) {
|
|
367
|
+
this.log('Checking and sanitizing story-related images for public S3 bucket...');
|
|
368
|
+
try {
|
|
369
|
+
const sanitizeResult = await this.presentation.sanitizeStoryImages(this.transport, {
|
|
370
|
+
storyId: story.id,
|
|
371
|
+
onProgress: (msg) => this.log(msg),
|
|
372
|
+
});
|
|
373
|
+
sanitizedImagesCount = sanitizeResult.updatedCount;
|
|
374
|
+
this.log(`Image sanitization completed: ${sanitizeResult.checkedCount} checked, ` +
|
|
375
|
+
`${sanitizeResult.uploadedCount} uploaded to public bucket, ` +
|
|
376
|
+
`${sanitizeResult.updatedCount} entity image URL(s) updated.`);
|
|
377
|
+
}
|
|
378
|
+
catch (e) {
|
|
379
|
+
this.log(`Failed to sanitize story images: ${e?.message || e}`, 'warn');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
this.log('Image sanitization handler is not configured on presentation storage.', 'warn');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
this.log(`Story repair completed: ${updatedSlidesCount} slide(s) updated, ` +
|
|
387
|
+
`${addedCharactersIds.length} character(s), ${addedArtifactsIds.length} artifact(s), ` +
|
|
388
|
+
`${addedLocationsIds.length} location(s) added` +
|
|
389
|
+
(repairOptions.sanitizeImages ? `, ${sanitizedImagesCount} image(s) sanitized.` : '.'));
|
|
390
|
+
return {
|
|
391
|
+
storyId: story.id,
|
|
392
|
+
name: story.name,
|
|
393
|
+
description: updatedStoryDescription || story.description || '',
|
|
394
|
+
slidesCount: slides.length,
|
|
395
|
+
updatedSlidesCount,
|
|
396
|
+
addedCharactersCount: addedCharactersIds.length,
|
|
397
|
+
addedArtifactsCount: addedArtifactsIds.length,
|
|
398
|
+
addedLocationsCount: addedLocationsIds.length,
|
|
399
|
+
repairedGrammar: repairOptions.grammarAndFlow,
|
|
400
|
+
sanitizedImagesCount,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
validateInput() {
|
|
404
|
+
if (!this.input?.storyId) {
|
|
405
|
+
throw new Error('Story is required.');
|
|
406
|
+
}
|
|
407
|
+
const opts = this.input.options || {
|
|
408
|
+
grammarAndFlow: true,
|
|
409
|
+
characters: true,
|
|
410
|
+
artifacts: true,
|
|
411
|
+
locations: true,
|
|
412
|
+
sanitizeImages: true,
|
|
413
|
+
};
|
|
414
|
+
const sanitizeImages = opts.sanitizeImages !== undefined ? !!opts.sanitizeImages : true;
|
|
415
|
+
const needsLlm = !!opts.grammarAndFlow || !!opts.characters || !!opts.artifacts || !!opts.locations;
|
|
416
|
+
if (!needsLlm && !sanitizeImages) {
|
|
417
|
+
throw new Error('At least one repair option must be selected.');
|
|
418
|
+
}
|
|
419
|
+
if (needsLlm && !this.input?.agentId) {
|
|
420
|
+
throw new Error('Agent is required.');
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
grammarAndFlow: !!opts.grammarAndFlow,
|
|
424
|
+
characters: !!opts.characters,
|
|
425
|
+
artifacts: !!opts.artifacts,
|
|
426
|
+
locations: !!opts.locations,
|
|
427
|
+
sanitizeImages,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
async resolveAgent() {
|
|
431
|
+
this.log('Resolving selected agent...');
|
|
432
|
+
const member = await this.context.getChatMember(this.chatId, this.input.agentId);
|
|
433
|
+
if (!member || !member.isAgent || !member.agentConfiguration) {
|
|
434
|
+
throw new Error('Selected agent is not an agentic member of this chat.');
|
|
435
|
+
}
|
|
436
|
+
return member;
|
|
437
|
+
}
|
|
438
|
+
async loadSlides(storyId) {
|
|
439
|
+
const response = await this.presentation.getSlides(this.transport, {
|
|
440
|
+
storyId,
|
|
441
|
+
slideNumber: -1,
|
|
442
|
+
direction: 'front',
|
|
443
|
+
limit: MAX_SLIDES_PER_STORY,
|
|
444
|
+
});
|
|
445
|
+
return [...(response?.slides || [])].sort((a, b) => (a.slideNumber || 0) - (b.slideNumber || 0));
|
|
446
|
+
}
|
|
447
|
+
buildPrompt(story, slides, existingLocations, existingCharacters, existingArtifacts, options) {
|
|
448
|
+
const payload = {
|
|
449
|
+
repairOptions: options,
|
|
450
|
+
story: {
|
|
451
|
+
id: story.id,
|
|
452
|
+
name: story.name,
|
|
453
|
+
description: story.description || '',
|
|
454
|
+
history: story.history || '',
|
|
455
|
+
moralMeaning: story.moralMeaning || '',
|
|
456
|
+
recommendedAge: story.recommendedAge || '',
|
|
457
|
+
},
|
|
458
|
+
existingLocations: existingLocations.map((l) => ({
|
|
459
|
+
id: l.id,
|
|
460
|
+
name: l.name,
|
|
461
|
+
description: l.description || '',
|
|
462
|
+
visualDescription: l.visualDescription || '',
|
|
463
|
+
})),
|
|
464
|
+
existingCharacters: existingCharacters.map((c) => ({
|
|
465
|
+
id: c.id,
|
|
466
|
+
name: c.name,
|
|
467
|
+
description: c.description || '',
|
|
468
|
+
visualDescription: c.visualDescription || '',
|
|
469
|
+
})),
|
|
470
|
+
existingArtifacts: existingArtifacts.map((a) => ({
|
|
471
|
+
id: a.id,
|
|
472
|
+
name: a.name,
|
|
473
|
+
description: a.description || '',
|
|
474
|
+
visualDescription: a.visualDescription || '',
|
|
475
|
+
})),
|
|
476
|
+
slides: slides.map((s) => ({
|
|
477
|
+
id: s.id,
|
|
478
|
+
slideNumber: s.slideNumber,
|
|
479
|
+
text: s.text || '',
|
|
480
|
+
illustrationDescription: s.illustrationDescription || '',
|
|
481
|
+
location: s.locationId || null,
|
|
482
|
+
characters: s.charactersIds || [],
|
|
483
|
+
artifacts: s.artifactsIds || [],
|
|
484
|
+
})),
|
|
485
|
+
};
|
|
486
|
+
return JSON.stringify(payload);
|
|
487
|
+
}
|
|
488
|
+
async createElements(kind, drafts, imageUrl, idMapping) {
|
|
489
|
+
const ids = [];
|
|
490
|
+
if (!drafts.length) {
|
|
491
|
+
return ids;
|
|
492
|
+
}
|
|
493
|
+
this.log(`Creating ${drafts.length} missing ${kind}(s)...`);
|
|
494
|
+
for (let i = 0; i < drafts.length; i++) {
|
|
495
|
+
const element = {
|
|
496
|
+
applicationId: this.transport.peer.rtseeAppId,
|
|
497
|
+
configurationId: '',
|
|
498
|
+
name: drafts[i].name,
|
|
499
|
+
description: drafts[i].description,
|
|
500
|
+
visualDescription: drafts[i].visualDescription,
|
|
501
|
+
imageUrl,
|
|
502
|
+
images: [],
|
|
503
|
+
tagIds: [],
|
|
504
|
+
tags: [],
|
|
505
|
+
};
|
|
506
|
+
try {
|
|
507
|
+
const created = kind === 'location'
|
|
508
|
+
? await this.presentation.createLocation(this.transport, element)
|
|
509
|
+
: kind === 'character'
|
|
510
|
+
? await this.presentation.createCharacter(this.transport, element)
|
|
511
|
+
: await this.presentation.createArtifact(this.transport, element);
|
|
512
|
+
ids.push(created.id);
|
|
513
|
+
if (idMapping) {
|
|
514
|
+
if (drafts[i].id) {
|
|
515
|
+
idMapping.set(drafts[i].id, created.id);
|
|
516
|
+
}
|
|
517
|
+
if (drafts[i].name) {
|
|
518
|
+
idMapping.set(drafts[i].name.toLowerCase(), created.id);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
this.log(`Created ${kind} ${i + 1}/${drafts.length}: "${created.name}".`);
|
|
522
|
+
}
|
|
523
|
+
catch (e) {
|
|
524
|
+
this.log(`Failed to create ${kind} "${drafts[i].name}": ${e?.message || e}`, 'warn');
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return ids;
|
|
528
|
+
}
|
|
529
|
+
async updateExistingElements(kind, existing, updates) {
|
|
530
|
+
if (!updates.length || !existing.length) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const existingById = new Map(existing.map((e) => [e.id, e]));
|
|
534
|
+
for (const upd of updates) {
|
|
535
|
+
if (!upd.id) {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
const orig = existingById.get(upd.id);
|
|
539
|
+
if (!orig) {
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const nextName = upd.name?.trim() || orig.name;
|
|
543
|
+
const nextDesc = upd.description?.trim() || orig.description;
|
|
544
|
+
const nextVisual = upd.visualDescription?.trim() || orig.visualDescription;
|
|
545
|
+
if (nextName === orig.name &&
|
|
546
|
+
nextDesc === orig.description &&
|
|
547
|
+
nextVisual === orig.visualDescription) {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
const updatedElement = {
|
|
551
|
+
...orig,
|
|
552
|
+
name: nextName,
|
|
553
|
+
description: nextDesc,
|
|
554
|
+
visualDescription: nextVisual,
|
|
555
|
+
};
|
|
556
|
+
try {
|
|
557
|
+
if (kind === 'location') {
|
|
558
|
+
await this.presentation.editLocation(this.transport, updatedElement);
|
|
559
|
+
}
|
|
560
|
+
else if (kind === 'character') {
|
|
561
|
+
await this.presentation.editCharacter(this.transport, updatedElement);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
await this.presentation.editArtifact(this.transport, updatedElement);
|
|
565
|
+
}
|
|
566
|
+
this.log(`Updated ${kind} "${nextName}".`);
|
|
567
|
+
}
|
|
568
|
+
catch (e) {
|
|
569
|
+
this.log(`Failed to update ${kind} "${nextName}": ${e?.message || e}`, 'warn');
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
parseRepairDraft(text) {
|
|
574
|
+
if (!text || !text.trim()) {
|
|
575
|
+
throw new Error('Repair response is empty.');
|
|
576
|
+
}
|
|
577
|
+
const start = text.indexOf('{');
|
|
578
|
+
const end = text.lastIndexOf('}');
|
|
579
|
+
if (start === -1 || end <= start) {
|
|
580
|
+
throw new Error('No JSON object found in the repair response.');
|
|
581
|
+
}
|
|
582
|
+
let candidate = text.substring(start, end + 1).trim();
|
|
583
|
+
candidate = candidate.replace(/^```json\s*/i, '').replace(/```$/i, '').trim();
|
|
584
|
+
let parsed;
|
|
585
|
+
try {
|
|
586
|
+
parsed = JSON.parse(candidate);
|
|
587
|
+
}
|
|
588
|
+
catch {
|
|
589
|
+
const cleaned = candidate.replace(/,\s*([\]}])/g, '$1');
|
|
590
|
+
parsed = JSON.parse(cleaned);
|
|
591
|
+
}
|
|
592
|
+
const slides = (Array.isArray(parsed.slides) ? parsed.slides : []).map((s) => ({
|
|
593
|
+
id: `${s?.id || ''}`.trim(),
|
|
594
|
+
slideNumber: typeof s?.slideNumber === 'number' ? s.slideNumber : undefined,
|
|
595
|
+
text: s?.text ? `${s.text}`.trim() : undefined,
|
|
596
|
+
illustrationDescription: s?.illustrationDescription
|
|
597
|
+
? `${s.illustrationDescription}`.trim()
|
|
598
|
+
: undefined,
|
|
599
|
+
location: s?.location === null
|
|
600
|
+
? null
|
|
601
|
+
: s?.location
|
|
602
|
+
? `${s.location}`.trim()
|
|
603
|
+
: undefined,
|
|
604
|
+
characters: Array.isArray(s?.characters)
|
|
605
|
+
? s.characters.map((c) => `${c}`.trim()).filter(Boolean)
|
|
606
|
+
: undefined,
|
|
607
|
+
artifacts: Array.isArray(s?.artifacts)
|
|
608
|
+
? s.artifacts.map((a) => `${a}`.trim()).filter(Boolean)
|
|
609
|
+
: undefined,
|
|
610
|
+
}));
|
|
611
|
+
return {
|
|
612
|
+
story: parsed.story
|
|
613
|
+
? {
|
|
614
|
+
description: parsed.story.description
|
|
615
|
+
? `${parsed.story.description}`.trim()
|
|
616
|
+
: undefined,
|
|
617
|
+
history: parsed.story.history ? `${parsed.story.history}`.trim() : undefined,
|
|
618
|
+
moralMeaning: parsed.story.moralMeaning
|
|
619
|
+
? `${parsed.story.moralMeaning}`.trim()
|
|
620
|
+
: undefined,
|
|
621
|
+
}
|
|
622
|
+
: undefined,
|
|
623
|
+
newLocations: this.parseElements(parsed.newLocations),
|
|
624
|
+
newCharacters: this.parseElements(parsed.newCharacters),
|
|
625
|
+
newArtifacts: this.parseElements(parsed.newArtifacts),
|
|
626
|
+
updatedLocations: this.parseElements(parsed.updatedLocations),
|
|
627
|
+
updatedCharacters: this.parseElements(parsed.updatedCharacters),
|
|
628
|
+
updatedArtifacts: this.parseElements(parsed.updatedArtifacts),
|
|
629
|
+
slides,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
parseElements(raw) {
|
|
633
|
+
if (!Array.isArray(raw)) {
|
|
634
|
+
return [];
|
|
635
|
+
}
|
|
636
|
+
const seen = new Set();
|
|
637
|
+
return raw
|
|
638
|
+
.map((e) => ({
|
|
639
|
+
id: e?.id ? `${e.id}`.trim() : undefined,
|
|
640
|
+
name: `${e?.name || ''}`.trim(),
|
|
641
|
+
description: `${e?.description || ''}`.trim(),
|
|
642
|
+
visualDescription: `${e?.visualDescription || ''}`.trim(),
|
|
643
|
+
}))
|
|
644
|
+
.filter((e) => {
|
|
645
|
+
const key = e.name.toLowerCase();
|
|
646
|
+
if (!e.name || seen.has(key)) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
seen.add(key);
|
|
650
|
+
return true;
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
sameStringArray(a, b) {
|
|
654
|
+
if (a.length !== b.length) {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
for (let i = 0; i < a.length; i++) {
|
|
658
|
+
if (a[i] !== b[i]) {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
exports.StoryRepairTask = StoryRepairTask;
|
|
666
|
+
function getStoryRepairTask(presentation) {
|
|
667
|
+
return {
|
|
668
|
+
name: common_1.STORY_REPAIR_TASK_NAME,
|
|
669
|
+
description: 'Repairs a Story by fixing grammar & flow and/or creating and linking missing characters, locations, and artifacts.',
|
|
670
|
+
create: (options) => new StoryRepairTask(options, presentation),
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
//# sourceMappingURL=StoryRepairTask.js.map
|