escribano 0.4.3 → 0.4.4
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.
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generates a work session summary from V3 processed TopicBlocks using LLM.
|
|
5
5
|
*/
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
6
7
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
7
8
|
import { homedir } from 'node:os';
|
|
8
9
|
import path, { dirname, resolve } from 'node:path';
|
|
9
10
|
import { fileURLToPath } from 'node:url';
|
|
10
11
|
import { log } from '../pipeline/context.js';
|
|
12
|
+
import { groupTopicBlocksIntoSubjects, saveSubjectsToDatabase, } from '../services/subject-grouping.js';
|
|
11
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
14
|
/**
|
|
13
15
|
* Generate a work session summary artifact from processed TopicBlocks.
|
|
@@ -19,21 +21,39 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
19
21
|
* @returns Generated artifact
|
|
20
22
|
*/
|
|
21
23
|
export async function generateSummaryV3(recordingId, repos, intelligence, options) {
|
|
22
|
-
log('info', `[Summary V3] Generating
|
|
24
|
+
log('info', `[Summary V3] Generating narrative for recording ${recordingId}...`);
|
|
23
25
|
// Get the recording
|
|
24
26
|
const recording = repos.recordings.findById(recordingId);
|
|
25
27
|
if (!recording) {
|
|
26
28
|
throw new Error(`Recording ${recordingId} not found`);
|
|
27
29
|
}
|
|
28
30
|
// Get TopicBlocks for this recording
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
+
const allTopicBlocks = repos.topicBlocks.findByRecording(recordingId);
|
|
32
|
+
if (allTopicBlocks.length === 0) {
|
|
31
33
|
throw new Error(`No TopicBlocks found for recording ${recordingId}. Run process-v3 first.`);
|
|
32
34
|
}
|
|
33
|
-
log('info', `[Summary V3] Found ${
|
|
35
|
+
log('info', `[Summary V3] Found ${allTopicBlocks.length} TopicBlocks`);
|
|
36
|
+
// Group TopicBlocks into subjects
|
|
37
|
+
log('info', '[Summary V3] Grouping TopicBlocks into subjects...');
|
|
38
|
+
const groupingResult = await groupTopicBlocksIntoSubjects(allTopicBlocks, intelligence, recordingId);
|
|
39
|
+
const { subjects } = groupingResult;
|
|
40
|
+
const { personalDuration, workDuration } = groupingResult;
|
|
41
|
+
// Save subjects to database
|
|
42
|
+
log('info', `[Summary V3] Saving ${subjects.length} subjects to database...`);
|
|
43
|
+
saveSubjectsToDatabase(subjects, recordingId, repos);
|
|
44
|
+
// Filter TopicBlocks based on personal/work classification
|
|
45
|
+
let topicBlocksToUse = allTopicBlocks;
|
|
46
|
+
if (!options.includePersonal) {
|
|
47
|
+
// Filter out blocks from personal subjects
|
|
48
|
+
const personalSubjectIds = new Set(subjects.filter((s) => s.isPersonal).map((s) => s.id));
|
|
49
|
+
topicBlocksToUse = allTopicBlocks.filter((block) => {
|
|
50
|
+
const subjectForBlock = subjects.find((s) => s.topicBlockIds.includes(block.id));
|
|
51
|
+
return !subjectForBlock?.isPersonal;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
34
54
|
// Build sections from TopicBlocks
|
|
35
55
|
const sections = [];
|
|
36
|
-
for (const block of
|
|
56
|
+
for (const block of topicBlocksToUse) {
|
|
37
57
|
const classification = JSON.parse(block.classification || '{}');
|
|
38
58
|
sections.push({
|
|
39
59
|
activity: classification.activity_type || 'unknown',
|
|
@@ -65,16 +85,48 @@ export async function generateSummaryV3(recordingId, repos, intelligence, option
|
|
|
65
85
|
await mkdir(outputDir, { recursive: true });
|
|
66
86
|
// Generate filename
|
|
67
87
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
68
|
-
const fileName = `${recordingId}-
|
|
88
|
+
const fileName = `${recordingId}-narrative-${timestamp}.md`;
|
|
69
89
|
const filePath = path.join(outputDir, fileName);
|
|
70
90
|
// Write to file
|
|
71
91
|
await writeFile(filePath, summaryContent, 'utf-8');
|
|
72
92
|
log('info', `[Summary V3] Summary saved to: ${filePath}`);
|
|
93
|
+
// Save to database
|
|
94
|
+
const artifactId = `artifact-${recordingId}-narrative-${Date.now()}`;
|
|
95
|
+
repos.artifacts.save({
|
|
96
|
+
id: artifactId,
|
|
97
|
+
recording_id: recordingId,
|
|
98
|
+
type: 'narrative',
|
|
99
|
+
content: summaryContent,
|
|
100
|
+
format: 'markdown',
|
|
101
|
+
source_block_ids: JSON.stringify(subjects.flatMap((s) => s.topicBlockIds)),
|
|
102
|
+
source_context_ids: null,
|
|
103
|
+
});
|
|
104
|
+
log('info', `[Summary V3] Saved to database: ${artifactId}`);
|
|
105
|
+
// Link subjects to artifact
|
|
106
|
+
repos.artifacts.linkSubjects(artifactId, subjects.map((s) => s.id));
|
|
107
|
+
log('info', `[Summary V3] Linked ${subjects.length} subjects to artifact`);
|
|
108
|
+
// Handle stdout/clipboard
|
|
109
|
+
if (options.printToStdout) {
|
|
110
|
+
console.log(`\n${summaryContent}\n`);
|
|
111
|
+
}
|
|
112
|
+
if (options.copyToClipboard && process.platform === 'darwin') {
|
|
113
|
+
try {
|
|
114
|
+
execSync('pbcopy', { input: summaryContent, encoding: 'utf-8' });
|
|
115
|
+
log('info', '[Summary V3] Copied to clipboard');
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
log('warn', `[Summary V3] Failed to copy to clipboard: ${error}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
73
121
|
return {
|
|
74
|
-
id:
|
|
122
|
+
id: artifactId,
|
|
75
123
|
recordingId,
|
|
124
|
+
format: 'narrative',
|
|
76
125
|
content: summaryContent,
|
|
77
126
|
filePath,
|
|
127
|
+
subjects,
|
|
128
|
+
personalDuration,
|
|
129
|
+
workDuration,
|
|
78
130
|
createdAt: new Date(),
|
|
79
131
|
};
|
|
80
132
|
}
|
package/dist/batch-context.js
CHANGED
|
@@ -14,6 +14,7 @@ import { execSync } from 'node:child_process';
|
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { generateArtifactV3, } from './actions/generate-artifact-v3.js';
|
|
17
|
+
import { generateSummaryV3 } from './actions/generate-summary-v3.js';
|
|
17
18
|
import { updateGlobalIndex } from './actions/outline-index.js';
|
|
18
19
|
import { processRecordingV3 } from './actions/process-recording-v3.js';
|
|
19
20
|
import { hasContentChanged, publishSummaryV3, updateRecordingOutlineMetadata, } from './actions/publish-summary-v3.js';
|
|
@@ -168,13 +169,28 @@ export async function processVideo(videoPath, ctx, options = {}) {
|
|
|
168
169
|
const artifactRunMetadata = collectRunMetadata(ctx.resourceTracker);
|
|
169
170
|
const pipelineResult = await withPipeline(recording.id, 'artifact', artifactRunMetadata, async () => {
|
|
170
171
|
console.log(`\nGenerating ${format} artifact...`);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
let generatedArtifact;
|
|
173
|
+
if (format === 'narrative') {
|
|
174
|
+
// Route narrative through the corrected path
|
|
175
|
+
generatedArtifact = await generateSummaryV3(recording.id, repos, llm, {
|
|
176
|
+
recordingId: recording.id,
|
|
177
|
+
outputDir: options.outputDir,
|
|
178
|
+
useTemplate: false,
|
|
179
|
+
includePersonal,
|
|
180
|
+
copyToClipboard,
|
|
181
|
+
printToStdout,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// Card and standup use the original path
|
|
186
|
+
generatedArtifact = await generateArtifactV3(recording.id, repos, llm, {
|
|
187
|
+
recordingId: recording.id,
|
|
188
|
+
format,
|
|
189
|
+
includePersonal,
|
|
190
|
+
copyToClipboard,
|
|
191
|
+
printToStdout,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
178
194
|
console.log(`Artifact saved: ${generatedArtifact.filePath}`);
|
|
179
195
|
if (generatedArtifact.workDuration > 0) {
|
|
180
196
|
const workMins = Math.round(generatedArtifact.workDuration / 60);
|