@redaksjon/protokoll-engine 0.1.4-dev.20260215203434.442d604 → 0.1.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.
Files changed (163) hide show
  1. package/dist/agentic/executor.d.ts.map +1 -1
  2. package/dist/agentic/types.d.ts +4 -1
  3. package/dist/agentic/types.d.ts.map +1 -1
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +8 -5
  7. package/dist/index.js.map +1 -1
  8. package/dist/index10.js +4 -3
  9. package/dist/index10.js.map +1 -1
  10. package/dist/index11.js +2 -2
  11. package/dist/index12.js +3 -124
  12. package/dist/index12.js.map +1 -1
  13. package/dist/index13.js +101 -100
  14. package/dist/index13.js.map +1 -1
  15. package/dist/index14.js +109 -281
  16. package/dist/index14.js.map +1 -1
  17. package/dist/index15.js +285 -89
  18. package/dist/index15.js.map +1 -1
  19. package/dist/index16.js +97 -104
  20. package/dist/index16.js.map +1 -1
  21. package/dist/index17.js +18 -179
  22. package/dist/index17.js.map +1 -1
  23. package/dist/index18.js +102 -48
  24. package/dist/index18.js.map +1 -1
  25. package/dist/index19.js +182 -16
  26. package/dist/index19.js.map +1 -1
  27. package/dist/index2.js +3 -3
  28. package/dist/index20.js +48 -100
  29. package/dist/index20.js.map +1 -1
  30. package/dist/index21.js +16 -23
  31. package/dist/index21.js.map +1 -1
  32. package/dist/index22.js +98 -42
  33. package/dist/index22.js.map +1 -1
  34. package/dist/index23.js +19 -112
  35. package/dist/index23.js.map +1 -1
  36. package/dist/index24.js +42 -323
  37. package/dist/index24.js.map +1 -1
  38. package/dist/index25.js +114 -52
  39. package/dist/index25.js.map +1 -1
  40. package/dist/index26.js +339 -37
  41. package/dist/index26.js.map +1 -1
  42. package/dist/index27.js +51 -121
  43. package/dist/index27.js.map +1 -1
  44. package/dist/index28.js +36 -155
  45. package/dist/index28.js.map +1 -1
  46. package/dist/index29.js +114 -150
  47. package/dist/index29.js.map +1 -1
  48. package/dist/index3.js +2 -2
  49. package/dist/index30.js +171 -161
  50. package/dist/index30.js.map +1 -1
  51. package/dist/index31.js +141 -401
  52. package/dist/index31.js.map +1 -1
  53. package/dist/index32.js +158 -146
  54. package/dist/index32.js.map +1 -1
  55. package/dist/index33.js +425 -130
  56. package/dist/index33.js.map +1 -1
  57. package/dist/index34.js +145 -40
  58. package/dist/index34.js.map +1 -1
  59. package/dist/index35.js +159 -83
  60. package/dist/index35.js.map +1 -1
  61. package/dist/index36.js +46 -439
  62. package/dist/index36.js.map +1 -1
  63. package/dist/index37.js +91 -419
  64. package/dist/index37.js.map +1 -1
  65. package/dist/index38.js +447 -66
  66. package/dist/index38.js.map +1 -1
  67. package/dist/index39.js +424 -115
  68. package/dist/index39.js.map +1 -1
  69. package/dist/index4.js +2 -2
  70. package/dist/index40.js +78 -290
  71. package/dist/index40.js.map +1 -1
  72. package/dist/index41.js +112 -44
  73. package/dist/index41.js.map +1 -1
  74. package/dist/index42.js +114 -143
  75. package/dist/index42.js.map +1 -1
  76. package/dist/index43.js +280 -207
  77. package/dist/index43.js.map +1 -1
  78. package/dist/index44.js +283 -41
  79. package/dist/index44.js.map +1 -1
  80. package/dist/index45.js +137 -38
  81. package/dist/index45.js.map +1 -1
  82. package/dist/index46.js +120 -32
  83. package/dist/index46.js.map +1 -1
  84. package/dist/index47.js +45 -4
  85. package/dist/index47.js.map +1 -1
  86. package/dist/index48.js +143 -43
  87. package/dist/index48.js.map +1 -1
  88. package/dist/index49.js +221 -34
  89. package/dist/index49.js.map +1 -1
  90. package/dist/index5.js +3 -3
  91. package/dist/index50.js +43 -233
  92. package/dist/index50.js.map +1 -1
  93. package/dist/index51.js +38 -156
  94. package/dist/index51.js.map +1 -1
  95. package/dist/index52.js +31 -75
  96. package/dist/index52.js.map +1 -1
  97. package/dist/index53.js +46 -73
  98. package/dist/index53.js.map +1 -1
  99. package/dist/index54.js +33 -144
  100. package/dist/index54.js.map +1 -1
  101. package/dist/index55.js +236 -5
  102. package/dist/index55.js.map +1 -1
  103. package/dist/index56.js +160 -5
  104. package/dist/index56.js.map +1 -1
  105. package/dist/index57.js +78 -14
  106. package/dist/index57.js.map +1 -1
  107. package/dist/index58.js +76 -2
  108. package/dist/index58.js.map +1 -1
  109. package/dist/index59.js +5 -14
  110. package/dist/index59.js.map +1 -1
  111. package/dist/index6.js +4 -4
  112. package/dist/index6.js.map +1 -1
  113. package/dist/index60.js +6 -2
  114. package/dist/index60.js.map +1 -1
  115. package/dist/index61.js +6 -4
  116. package/dist/index61.js.map +1 -1
  117. package/dist/index62.js +151 -0
  118. package/dist/index62.js.map +1 -0
  119. package/dist/index63.js +17 -0
  120. package/dist/index63.js.map +1 -0
  121. package/dist/index64.js +4 -0
  122. package/dist/index64.js.map +1 -0
  123. package/dist/index65.js +17 -0
  124. package/dist/index65.js.map +1 -0
  125. package/dist/index66.js +4 -0
  126. package/dist/index66.js.map +1 -0
  127. package/dist/index67.js +6 -0
  128. package/dist/index67.js.map +1 -0
  129. package/dist/index7.js +2 -2
  130. package/dist/index8.js +1 -1
  131. package/dist/index9.js +4 -4
  132. package/dist/phases/simple-replace.d.ts +2 -1
  133. package/dist/phases/simple-replace.d.ts.map +1 -1
  134. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  135. package/dist/pipeline/types.d.ts +3 -0
  136. package/dist/pipeline/types.d.ts.map +1 -1
  137. package/dist/routing/classifier.d.ts +2 -1
  138. package/dist/routing/classifier.d.ts.map +1 -1
  139. package/dist/routing/index.d.ts +2 -1
  140. package/dist/routing/index.d.ts.map +1 -1
  141. package/dist/routing/types.d.ts +1 -1
  142. package/dist/routing/types.d.ts.map +1 -1
  143. package/dist/transcript/index.d.ts +1 -0
  144. package/dist/transcript/index.d.ts.map +1 -1
  145. package/dist/transcript/operations.d.ts +14 -1
  146. package/dist/transcript/operations.d.ts.map +1 -1
  147. package/dist/transcript/upload-utils.d.ts +80 -0
  148. package/dist/transcript/upload-utils.d.ts.map +1 -0
  149. package/dist/util/enhancement-logger.d.ts +19 -0
  150. package/dist/util/enhancement-logger.d.ts.map +1 -0
  151. package/dist/util/metadata.d.ts +1 -0
  152. package/dist/util/metadata.d.ts.map +1 -1
  153. package/dist/weighting/builder.d.ts +126 -0
  154. package/dist/weighting/builder.d.ts.map +1 -0
  155. package/dist/weighting/index.d.ts +12 -0
  156. package/dist/weighting/index.d.ts.map +1 -0
  157. package/dist/weighting/prepositioning.d.ts +55 -0
  158. package/dist/weighting/prepositioning.d.ts.map +1 -0
  159. package/dist/weighting/provider.d.ts +78 -0
  160. package/dist/weighting/provider.d.ts.map +1 -0
  161. package/dist/weighting/types.d.ts +119 -0
  162. package/dist/weighting/types.d.ts.map +1 -0
  163. package/package.json +2 -2
package/dist/index14.js CHANGED
@@ -1,296 +1,124 @@
1
- const formatMetadataMarkdown = (metadata) => {
2
- const lines = [];
3
- if (metadata.title) {
4
- lines.push(`# ${metadata.title}`);
5
- lines.push("");
6
- }
7
- lines.push("## Metadata");
8
- lines.push("");
9
- if (metadata.date) {
10
- const dateStr = metadata.date.toLocaleDateString("en-US", {
11
- year: "numeric",
12
- month: "long",
13
- day: "numeric"
1
+ import ffmpeg from 'fluent-ffmpeg';
2
+ import path__default from 'node:path';
3
+ import { create as create$1 } from './index13.js';
4
+
5
+ const ffprobeAsync = (filePath) => {
6
+ return new Promise((resolve, reject) => {
7
+ ffmpeg.ffprobe(filePath, (err, metadata) => {
8
+ if (err) return reject(err);
9
+ resolve(metadata);
14
10
  });
15
- lines.push(`**Date**: ${dateStr}`);
16
- if (metadata.recordingTime) {
17
- lines.push(`**Time**: ${metadata.recordingTime}`);
18
- } else {
19
- const timeStr = metadata.date.toLocaleTimeString("en-US", {
20
- hour: "2-digit",
21
- minute: "2-digit",
22
- hour12: true
23
- });
24
- lines.push(`**Time**: ${timeStr}`);
25
- }
26
- }
27
- lines.push("");
28
- if (metadata.project) {
29
- lines.push(`**Project**: ${metadata.project}`);
30
- if (metadata.projectId) {
31
- lines.push(`**Project ID**: \`${metadata.projectId}\``);
32
- }
33
- lines.push("");
34
- }
35
- if (metadata.routing) {
36
- lines.push("### Routing");
37
- lines.push("");
38
- lines.push(`**Destination**: ${metadata.routing.destination}`);
39
- lines.push(`**Confidence**: ${(metadata.routing.confidence * 100).toFixed(1)}%`);
40
- lines.push("");
41
- if (metadata.routing.signals.length > 0) {
42
- lines.push("**Classification Signals**:");
43
- for (const signal of metadata.routing.signals) {
44
- const signalType = signal.type.replace(/_/g, " ");
45
- const weight = (signal.weight * 100).toFixed(0);
46
- lines.push(`- ${signalType}: "${signal.value}" (${weight}% weight)`);
47
- }
48
- lines.push("");
49
- }
50
- if (metadata.routing.reasoning) {
51
- lines.push(`**Reasoning**: ${metadata.routing.reasoning}`);
52
- lines.push("");
53
- }
54
- }
55
- if (metadata.tags && metadata.tags.length > 0) {
56
- lines.push("**Tags**: " + metadata.tags.map((tag) => `\`${tag}\``).join(", "));
57
- lines.push("");
58
- }
59
- if (metadata.duration) {
60
- lines.push(`**Duration**: ${metadata.duration}`);
61
- lines.push("");
62
- }
63
- lines.push("---");
64
- lines.push("");
65
- return lines.join("\n");
11
+ });
66
12
  };
67
- const formatEntityMetadataMarkdown = (metadata) => {
68
- if (!metadata.entities) {
69
- return "";
70
- }
71
- const lines = [];
72
- lines.push("");
73
- lines.push("---");
74
- lines.push("");
75
- lines.push("## Entity References");
76
- lines.push("");
77
- lines.push("<!-- Machine-readable entity metadata for indexing and querying -->");
78
- lines.push("");
79
- if (metadata.entities.people && metadata.entities.people.length > 0) {
80
- lines.push("### People");
81
- lines.push("");
82
- for (const person of metadata.entities.people) {
83
- lines.push(`- \`${person.id}\`: ${person.name}`);
84
- }
85
- lines.push("");
86
- }
87
- if (metadata.entities.projects && metadata.entities.projects.length > 0) {
88
- lines.push("### Projects");
89
- lines.push("");
90
- for (const project of metadata.entities.projects) {
91
- lines.push(`- \`${project.id}\`: ${project.name}`);
92
- }
93
- lines.push("");
94
- }
95
- if (metadata.entities.terms && metadata.entities.terms.length > 0) {
96
- lines.push("### Terms");
97
- lines.push("");
98
- for (const term of metadata.entities.terms) {
99
- lines.push(`- \`${term.id}\`: ${term.name}`);
100
- }
101
- lines.push("");
102
- }
103
- if (metadata.entities.companies && metadata.entities.companies.length > 0) {
104
- lines.push("### Companies");
105
- lines.push("");
106
- for (const company of metadata.entities.companies) {
107
- lines.push(`- \`${company.id}\`: ${company.name}`);
13
+ const create = (logger) => {
14
+ const storage$1 = create$1({ log: logger.debug });
15
+ const getAudioCreationTime = async (filePath) => {
16
+ try {
17
+ const metadata = await ffprobeAsync(filePath);
18
+ const formatTags = metadata?.format?.tags;
19
+ if (formatTags?.creation_time) {
20
+ logger.debug("Found creation_time in format tags: %s", formatTags.creation_time);
21
+ return new Date(formatTags.creation_time);
22
+ }
23
+ if (metadata?.streams?.length > 0) {
24
+ for (const stream of metadata.streams) {
25
+ if (stream.tags?.creation_time) {
26
+ logger.debug("Found creation_time in stream tags: %s", stream.tags.creation_time);
27
+ return new Date(stream.tags.creation_time);
28
+ }
29
+ }
30
+ }
31
+ logger.debug("No creation_time found in audio file metadata");
32
+ return null;
33
+ } catch (error) {
34
+ logger.error("Error extracting creation time from audio file: %s", error);
35
+ return null;
108
36
  }
109
- lines.push("");
110
- }
111
- return lines.join("\n");
112
- };
113
- const parseEntityMetadata = (content) => {
114
- const headerIndex = content.indexOf("## Entity References");
115
- if (headerIndex === -1) {
116
- return void 0;
117
- }
118
- let contentStart = headerIndex + "## Entity References".length;
119
- while (contentStart < content.length && (content[contentStart] === "\n" || content[contentStart] === "\r" || content[contentStart] === " " || content[contentStart] === " ")) {
120
- contentStart++;
121
- }
122
- const remainingContent = content.substring(contentStart);
123
- const nextHeaderMatch = remainingContent.match(/\n## /);
124
- const sectionContent = nextHeaderMatch ? remainingContent.substring(0, nextHeaderMatch.index) : remainingContent;
125
- const entities = {
126
- people: [],
127
- projects: [],
128
- terms: [],
129
- companies: []
130
37
  };
131
- const parseEntities = (type) => {
132
- const typeMap = {
133
- "People": "person",
134
- "Projects": "project",
135
- "Terms": "term",
136
- "Companies": "company"
137
- };
138
- const entityType = typeMap[type];
139
- const sectionHeader = `### ${type}`;
140
- const sectionStart = sectionContent.indexOf(sectionHeader);
141
- if (sectionStart === -1) return [];
142
- const headerEnd = sectionStart + sectionHeader.length;
143
- let sectionTextStart = headerEnd;
144
- while (sectionTextStart < sectionContent.length && (sectionContent[sectionTextStart] === "\n" || sectionContent[sectionTextStart] === "\r" || sectionContent[sectionTextStart] === " ")) {
145
- sectionTextStart++;
38
+ const getFileSize = async (filePath) => {
39
+ try {
40
+ return await storage$1.getFileSize(filePath);
41
+ } catch (error) {
42
+ logger.error("Error getting file size: %s", error);
43
+ throw new Error(`Failed to get file size for ${filePath}: ${error}`);
146
44
  }
147
- const afterSection = sectionContent.substring(sectionTextStart);
148
- const nextSection = afterSection.search(/\n###/);
149
- const sectionText = nextSection === -1 ? afterSection : afterSection.substring(0, nextSection);
150
- const items = [];
151
- const lines = sectionText.split("\n");
152
- for (const line of lines) {
153
- const trimmed = line.trim();
154
- const match = trimmed.match(/^- `([^`]+)`:\s*(.+)$/);
155
- if (match) {
156
- items.push({
157
- id: match[1],
158
- name: match[2].trim(),
159
- type: entityType
45
+ };
46
+ const splitAudioFile = async (filePath, outputDir, maxSizeBytes) => {
47
+ try {
48
+ const metadata = await ffprobeAsync(filePath);
49
+ const duration = parseFloat(metadata.format.duration);
50
+ const fileSize = await getFileSize(filePath);
51
+ const segmentCount = Math.ceil(fileSize / maxSizeBytes);
52
+ const segmentDuration = duration / segmentCount;
53
+ logger.debug(`Splitting ${filePath} (${fileSize} bytes) into ${segmentCount} segments of ~${segmentDuration} seconds each`);
54
+ await storage$1.createDirectory(outputDir);
55
+ const outputFiles = [];
56
+ const fileExt = path__default.extname(filePath);
57
+ const fileName = path__default.basename(filePath, fileExt);
58
+ const promises = [];
59
+ for (let i = 0; i < segmentCount; i++) {
60
+ const startTime = i * segmentDuration;
61
+ const outputPath = path__default.join(outputDir, `${fileName}_part${i + 1}${fileExt}`);
62
+ outputFiles.push(outputPath);
63
+ const promise = new Promise((resolve, reject) => {
64
+ ffmpeg(filePath).setStartTime(startTime).setDuration(segmentDuration).output(outputPath).on("end", () => {
65
+ logger.debug(`Created segment ${i + 1}/${segmentCount}: ${outputPath}`);
66
+ resolve();
67
+ }).on("error", (err) => {
68
+ logger.error(`Error creating segment ${i + 1}/${segmentCount}: ${err}`);
69
+ reject(err);
70
+ }).run();
160
71
  });
72
+ promises.push(promise);
161
73
  }
74
+ await Promise.all(promises);
75
+ return outputFiles;
76
+ } catch (error) {
77
+ logger.error("Error splitting audio file: %s", error);
78
+ throw new Error(`Failed to split audio file ${filePath}: ${error}`);
162
79
  }
163
- return items;
164
- };
165
- entities.people = parseEntities("People");
166
- entities.projects = parseEntities("Projects");
167
- entities.terms = parseEntities("Terms");
168
- entities.companies = parseEntities("Companies");
169
- const hasEntities = entities.people.length > 0 || entities.projects.length > 0 || entities.terms.length > 0 || entities.companies.length > 0;
170
- return hasEntities ? entities : void 0;
171
- };
172
- const createRoutingMetadata = (decision) => {
173
- return {
174
- destination: decision.destination.path,
175
- confidence: decision.confidence,
176
- signals: decision.signals,
177
- reasoning: decision.reasoning
178
80
  };
179
- };
180
- const formatDuration = (seconds) => {
181
- const minutes = Math.floor(seconds / 60);
182
- const secs = Math.round(seconds % 60);
183
- if (minutes === 0) {
184
- return `${secs}s`;
185
- }
186
- if (secs === 0) {
187
- return `${minutes}m`;
188
- }
189
- return `${minutes}m ${secs}s`;
190
- };
191
- const formatTime = (date) => {
192
- return date.toLocaleTimeString("en-US", {
193
- hour: "2-digit",
194
- minute: "2-digit",
195
- hour12: true
196
- });
197
- };
198
- const extractTopicFromSignals = (signals) => {
199
- const topicSignal = signals.find((s) => s.type === "topic" || s.type === "context_type");
200
- return topicSignal?.value;
201
- };
202
- const extractTagsFromSignals = (signals) => {
203
- const tags = signals.filter((s) => s.type !== "context_type").map((s) => s.value).filter((v) => typeof v === "string");
204
- return Array.from(new Set(tags));
205
- };
206
- const VALID_STATUSES = [
207
- "initial",
208
- "enhanced",
209
- "reviewed",
210
- "in_progress",
211
- "closed",
212
- "archived"
213
- ];
214
- const isValidStatus = (status) => {
215
- return VALID_STATUSES.includes(status);
216
- };
217
- const generateTaskId = () => {
218
- const timestamp = Date.now();
219
- const random = Math.random().toString(36).substring(2, 8);
220
- return `task-${timestamp}-${random}`;
221
- };
222
- const createTask = (description) => {
223
- return {
224
- id: generateTaskId(),
225
- description,
226
- status: "open",
227
- created: (/* @__PURE__ */ new Date()).toISOString()
228
- };
229
- };
230
- const updateStatus = (metadata, newStatus) => {
231
- const oldStatus = metadata.status;
232
- if (oldStatus === newStatus) {
233
- return metadata;
234
- }
235
- const transition = {
236
- from: oldStatus || "reviewed",
237
- to: newStatus,
238
- at: (/* @__PURE__ */ new Date()).toISOString()
239
- };
240
- return {
241
- ...metadata,
242
- status: newStatus,
243
- history: [...metadata.history || [], transition]
244
- };
245
- };
246
- const applyLifecycleDefaults = (metadata) => {
247
- return {
248
- ...metadata,
249
- status: metadata.status ?? "reviewed",
250
- history: metadata.history ?? [],
251
- tasks: metadata.tasks ?? []
252
- };
253
- };
254
- const completeTask = (metadata, taskId) => {
255
- const tasks = metadata.tasks || [];
256
- const taskIndex = tasks.findIndex((t) => t.id === taskId);
257
- if (taskIndex === -1) {
258
- throw new Error(`Task not found: ${taskId}`);
259
- }
260
- const now = (/* @__PURE__ */ new Date()).toISOString();
261
- const updatedTasks = [...tasks];
262
- updatedTasks[taskIndex] = {
263
- ...updatedTasks[taskIndex],
264
- status: "done",
265
- changed: now,
266
- completed: now
267
- };
268
- return {
269
- ...metadata,
270
- tasks: updatedTasks
271
- };
272
- };
273
- const deleteTask = (metadata, taskId) => {
274
- const tasks = metadata.tasks || [];
275
- const taskIndex = tasks.findIndex((t) => t.id === taskId);
276
- if (taskIndex === -1) {
277
- throw new Error(`Task not found: ${taskId}`);
278
- }
279
- return {
280
- ...metadata,
281
- tasks: tasks.filter((t) => t.id !== taskId)
81
+ const convertToSupportedFormat = async (filePath, outputDir, forceConversion = false) => {
82
+ try {
83
+ const fileExt = path__default.extname(filePath).toLowerCase();
84
+ const supportedFormats = [".flac", ".m4a", ".mp3", ".mp4", ".mpeg", ".mpga", ".oga", ".ogg", ".wav", ".webm"];
85
+ if (supportedFormats.includes(fileExt) && !forceConversion) {
86
+ logger.debug(`File ${filePath} is already in a supported format: ${fileExt}`);
87
+ return filePath;
88
+ }
89
+ if (forceConversion && fileExt === ".mp3") {
90
+ logger.debug(`File ${filePath} is already MP3 (compressed format)`);
91
+ return filePath;
92
+ }
93
+ logger.info(`Converting ${fileExt} file to mp3 for transcription...`);
94
+ const fileName = path__default.basename(filePath, fileExt);
95
+ const outputPath = path__default.join(outputDir, `${fileName}.mp3`);
96
+ if (await storage$1.exists(outputPath)) {
97
+ logger.debug(`Converted file already exists: ${outputPath}`);
98
+ return outputPath;
99
+ }
100
+ await storage$1.createDirectory(outputDir);
101
+ return new Promise((resolve, reject) => {
102
+ ffmpeg(filePath).toFormat("mp3").audioBitrate("128k").output(outputPath).on("end", () => {
103
+ logger.info(`Successfully converted to: ${outputPath}`);
104
+ resolve(outputPath);
105
+ }).on("error", (err) => {
106
+ logger.error(`Error converting audio file: ${err}`);
107
+ reject(new Error(`Failed to convert ${filePath} to mp3: ${err.message}`));
108
+ }).run();
109
+ });
110
+ } catch (error) {
111
+ logger.error("Error in convertToSupportedFormat: %s", error);
112
+ throw new Error(`Failed to convert audio file ${filePath}: ${error}`);
113
+ }
282
114
  };
283
- };
284
- const addTask = (metadata, description) => {
285
- const task = createTask(description);
286
115
  return {
287
- metadata: {
288
- ...metadata,
289
- tasks: [...metadata.tasks || [], task]
290
- },
291
- task
116
+ getAudioCreationTime,
117
+ getFileSize,
118
+ splitAudioFile,
119
+ convertToSupportedFormat
292
120
  };
293
121
  };
294
122
 
295
- export { VALID_STATUSES, addTask, applyLifecycleDefaults, completeTask, createRoutingMetadata, createTask, deleteTask, extractTagsFromSignals, extractTopicFromSignals, formatDuration, formatEntityMetadataMarkdown, formatMetadataMarkdown, formatTime, generateTaskId, isValidStatus, parseEntityMetadata, updateStatus };
123
+ export { create };
296
124
  //# sourceMappingURL=index14.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index14.js","sources":["../src/util/metadata.ts"],"sourcesContent":["import * as Routing from '@/routing';\n\n// ============================================================================\n// Lifecycle Types\n// ============================================================================\n\n/**\n * Transcript lifecycle status\n * - initial: Whisper transcription complete\n * - enhanced: Context-aware enhancement complete\n * - reviewed: User has reviewed the transcript\n * - in_progress: Has outstanding tasks to complete\n * - closed: All work complete, no pending tasks\n * - archived: Archived for long-term storage\n */\nexport type TranscriptStatus = 'initial' | 'enhanced' | 'reviewed' | 'in_progress' | 'closed' | 'archived';\n\n/**\n * Record of a status transition with timestamp\n */\nexport interface StatusTransition {\n from: TranscriptStatus;\n to: TranscriptStatus;\n at: string; // ISO 8601 timestamp\n}\n\n/**\n * A follow-up task associated with a transcript\n */\nexport interface Task {\n id: string; // generated unique ID (e.g., task-1234567890-abc123)\n description: string;\n status: 'open' | 'done';\n created: string; // ISO 8601 timestamp\n changed?: string; // ISO 8601 timestamp - when last modified\n completed?: string; // ISO 8601 timestamp - when marked done\n}\n\n// ============================================================================\n// Entity Types\n// ============================================================================\n\nexport interface EntityReference {\n id: string;\n name: string;\n type: 'person' | 'project' | 'term' | 'company';\n}\n\nexport interface TranscriptMetadata {\n // Core fields\n title?: string;\n project?: string;\n projectId?: string;\n routing?: RoutingMetadata;\n tags?: string[];\n date?: Date;\n recordingTime?: string;\n confidence?: number;\n duration?: string;\n \n // Lifecycle fields\n status?: TranscriptStatus;\n history?: StatusTransition[];\n tasks?: Task[];\n \n // Entity references - entities mentioned/used in this transcript\n entities?: {\n people?: EntityReference[];\n projects?: EntityReference[];\n terms?: EntityReference[];\n companies?: EntityReference[];\n };\n}\n\nexport interface RoutingMetadata {\n destination: string;\n confidence: number;\n signals: Routing.ClassificationSignal[];\n reasoning: string;\n}\n\n/**\n * Format metadata as Markdown heading section\n */\nexport const formatMetadataMarkdown = (metadata: TranscriptMetadata): string => {\n const lines: string[] = [];\n \n // Title section\n if (metadata.title) {\n lines.push(`# ${metadata.title}`);\n lines.push('');\n }\n \n // Metadata frontmatter as readable markdown\n lines.push('## Metadata');\n lines.push('');\n \n // Date and Time\n if (metadata.date) {\n const dateStr = metadata.date.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n lines.push(`**Date**: ${dateStr}`);\n \n if (metadata.recordingTime) {\n lines.push(`**Time**: ${metadata.recordingTime}`);\n } else {\n const timeStr = metadata.date.toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: true\n });\n lines.push(`**Time**: ${timeStr}`);\n }\n }\n \n lines.push('');\n \n // Project\n if (metadata.project) {\n lines.push(`**Project**: ${metadata.project}`);\n if (metadata.projectId) {\n lines.push(`**Project ID**: \\`${metadata.projectId}\\``);\n }\n lines.push('');\n }\n \n // Routing Information\n if (metadata.routing) {\n lines.push('### Routing');\n lines.push('');\n lines.push(`**Destination**: ${metadata.routing.destination}`);\n lines.push(`**Confidence**: ${(metadata.routing.confidence * 100).toFixed(1)}%`);\n lines.push('');\n \n if (metadata.routing.signals.length > 0) {\n lines.push('**Classification Signals**:');\n for (const signal of metadata.routing.signals) {\n const signalType = signal.type.replace(/_/g, ' ');\n const weight = (signal.weight * 100).toFixed(0);\n lines.push(`- ${signalType}: \"${signal.value}\" (${weight}% weight)`);\n }\n lines.push('');\n }\n \n if (metadata.routing.reasoning) {\n lines.push(`**Reasoning**: ${metadata.routing.reasoning}`);\n lines.push('');\n }\n }\n \n // Tags\n if (metadata.tags && metadata.tags.length > 0) {\n lines.push('**Tags**: ' + metadata.tags.map(tag => `\\`${tag}\\``).join(', '));\n lines.push('');\n }\n \n // Duration\n if (metadata.duration) {\n lines.push(`**Duration**: ${metadata.duration}`);\n lines.push('');\n }\n \n // Separator\n lines.push('---');\n lines.push('');\n \n return lines.join('\\n');\n};\n\n/**\n * Format entity metadata as Markdown footer section\n * This goes at the END of the transcript for machine readability\n */\nexport const formatEntityMetadataMarkdown = (metadata: TranscriptMetadata): string => {\n if (!metadata.entities) {\n return '';\n }\n \n const lines: string[] = [];\n lines.push('');\n lines.push('---');\n lines.push('');\n lines.push('## Entity References');\n lines.push('');\n lines.push('<!-- Machine-readable entity metadata for indexing and querying -->');\n lines.push('');\n \n // People\n if (metadata.entities.people && metadata.entities.people.length > 0) {\n lines.push('### People');\n lines.push('');\n for (const person of metadata.entities.people) {\n lines.push(`- \\`${person.id}\\`: ${person.name}`);\n }\n lines.push('');\n }\n \n // Projects\n if (metadata.entities.projects && metadata.entities.projects.length > 0) {\n lines.push('### Projects');\n lines.push('');\n for (const project of metadata.entities.projects) {\n lines.push(`- \\`${project.id}\\`: ${project.name}`);\n }\n lines.push('');\n }\n \n // Terms\n if (metadata.entities.terms && metadata.entities.terms.length > 0) {\n lines.push('### Terms');\n lines.push('');\n for (const term of metadata.entities.terms) {\n lines.push(`- \\`${term.id}\\`: ${term.name}`);\n }\n lines.push('');\n }\n \n // Companies\n if (metadata.entities.companies && metadata.entities.companies.length > 0) {\n lines.push('### Companies');\n lines.push('');\n for (const company of metadata.entities.companies) {\n lines.push(`- \\`${company.id}\\`: ${company.name}`);\n }\n lines.push('');\n }\n \n return lines.join('\\n');\n};\n\n/**\n * Parse entity metadata from a transcript\n * Reads the Entity References section if present\n */\nexport const parseEntityMetadata = (content: string): TranscriptMetadata['entities'] | undefined => {\n // Find the Entity References section\n // Look for \"## Entity References\" and capture everything after it until the next \"##\" header or end of content\n const headerIndex = content.indexOf('## Entity References');\n if (headerIndex === -1) {\n return undefined;\n }\n \n // Find the start of the content (after the header and any whitespace/newlines)\n let contentStart = headerIndex + '## Entity References'.length;\n // Skip whitespace and newlines\n while (contentStart < content.length && (content[contentStart] === '\\n' || content[contentStart] === '\\r' || content[contentStart] === ' ' || content[contentStart] === '\\t')) {\n contentStart++;\n }\n \n // Find the end - look for next \"##\" at start of line or end of content\n const remainingContent = content.substring(contentStart);\n const nextHeaderMatch = remainingContent.match(/\\n## /);\n const sectionContent = nextHeaderMatch \n ? remainingContent.substring(0, nextHeaderMatch.index)\n : remainingContent;\n const entities: NonNullable<TranscriptMetadata['entities']> = {\n people: [],\n projects: [],\n terms: [],\n companies: [],\n };\n \n // Parse each entity type\n const parseEntities = (type: 'People' | 'Projects' | 'Terms' | 'Companies'): EntityReference[] => {\n // Map plural type names to singular entity types\n const typeMap: Record<string, 'person' | 'project' | 'term' | 'company'> = {\n 'People': 'person',\n 'Projects': 'project',\n 'Terms': 'term',\n 'Companies': 'company',\n };\n \n const entityType = typeMap[type];\n \n // Find the section for this type\n const sectionHeader = `### ${type}`;\n const sectionStart = sectionContent.indexOf(sectionHeader);\n if (sectionStart === -1) return [];\n \n // Find the end (next ### or end of content)\n // Skip past the header line (including newline)\n const headerEnd = sectionStart + sectionHeader.length;\n let sectionTextStart = headerEnd;\n // Skip whitespace and newlines after the header\n while (sectionTextStart < sectionContent.length && \n (sectionContent[sectionTextStart] === '\\n' || sectionContent[sectionTextStart] === '\\r' || sectionContent[sectionTextStart] === ' ')) {\n sectionTextStart++;\n }\n \n const afterSection = sectionContent.substring(sectionTextStart);\n const nextSection = afterSection.search(/\\n###/);\n const sectionText = nextSection === -1 ? afterSection : afterSection.substring(0, nextSection);\n \n // Extract items - match format: \"- `id`: name\"\n // Match bullet point with backticked ID and name\n const items: EntityReference[] = [];\n // Use multiline regex to match across lines, look for \"- `id`: name\" pattern\n const lines = sectionText.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n // Match: \"- `id`: name\" or \"- `id`:name\" (with or without space after colon)\n const match = trimmed.match(/^- `([^`]+)`:\\s*(.+)$/);\n if (match) {\n items.push({\n id: match[1],\n name: match[2].trim(),\n type: entityType,\n });\n }\n }\n \n return items;\n };\n \n entities.people = parseEntities('People');\n entities.projects = parseEntities('Projects');\n entities.terms = parseEntities('Terms');\n entities.companies = parseEntities('Companies');\n \n // Only return if we found any entities\n const hasEntities = \n entities.people.length > 0 ||\n entities.projects.length > 0 ||\n entities.terms.length > 0 ||\n entities.companies.length > 0;\n \n return hasEntities ? entities : undefined;\n};\n\n/**\n * Extract routing metadata from a RouteDecision\n */\nexport const createRoutingMetadata = (decision: Routing.RouteDecision): RoutingMetadata => {\n return {\n destination: decision.destination.path,\n confidence: decision.confidence,\n signals: decision.signals,\n reasoning: decision.reasoning,\n };\n};\n\n/**\n * Format duration in seconds to readable format (e.g., \"2m 30s\")\n */\nexport const formatDuration = (seconds: number): string => {\n const minutes = Math.floor(seconds / 60);\n const secs = Math.round(seconds % 60);\n \n if (minutes === 0) {\n return `${secs}s`;\n }\n \n if (secs === 0) {\n return `${minutes}m`;\n }\n \n return `${minutes}m ${secs}s`;\n};\n\n/**\n * Format time as HH:MM AM/PM\n */\nexport const formatTime = (date: Date): string => {\n return date.toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n hour12: true\n });\n};\n\n/**\n * Extract topic from routing signals\n */\nexport const extractTopicFromSignals = (signals: Routing.ClassificationSignal[]): string | undefined => {\n const topicSignal = signals.find(s => s.type === 'topic' || s.type === 'context_type');\n return topicSignal?.value;\n};\n\n/**\n * Extract all tags from routing signals\n * Tags are deduplicated to avoid duplicates from multiple signal sources\n */\nexport const extractTagsFromSignals = (signals: Routing.ClassificationSignal[]): string[] => {\n const tags = signals\n .filter(s => s.type !== 'context_type') // Skip generic context type\n .map(s => s.value)\n .filter((v): v is string => typeof v === 'string');\n \n // Deduplicate tags using Set\n return Array.from(new Set(tags));\n};\n\n// ============================================================================\n// Lifecycle Utilities\n// ============================================================================\n\n/**\n * Valid transcript statuses for validation\n */\nexport const VALID_STATUSES: TranscriptStatus[] = [\n 'initial', 'enhanced', 'reviewed', 'in_progress', 'closed', 'archived'\n];\n\n/**\n * Check if a string is a valid TranscriptStatus\n */\nexport const isValidStatus = (status: string): status is TranscriptStatus => {\n return VALID_STATUSES.includes(status as TranscriptStatus);\n};\n\n/**\n * Generate a unique task ID\n */\nexport const generateTaskId = (): string => {\n const timestamp = Date.now();\n const random = Math.random().toString(36).substring(2, 8);\n return `task-${timestamp}-${random}`;\n};\n\n/**\n * Create a new task\n */\nexport const createTask = (description: string): Task => {\n return {\n id: generateTaskId(),\n description,\n status: 'open',\n created: new Date().toISOString(),\n };\n};\n\n/**\n * Update transcript status and record the transition in history\n * Returns unchanged metadata if status is the same (no duplicate history)\n */\nexport const updateStatus = (\n metadata: TranscriptMetadata,\n newStatus: TranscriptStatus\n): TranscriptMetadata => {\n const oldStatus = metadata.status;\n \n // Don't add duplicate history if status unchanged\n if (oldStatus === newStatus) {\n return metadata;\n }\n \n const transition: StatusTransition = {\n from: oldStatus || 'reviewed',\n to: newStatus,\n at: new Date().toISOString(),\n };\n \n return {\n ...metadata,\n status: newStatus,\n history: [...(metadata.history || []), transition],\n };\n};\n\n/**\n * Apply default lifecycle fields to metadata\n * Used during lazy migration of old-format transcripts\n */\nexport const applyLifecycleDefaults = (metadata: TranscriptMetadata): TranscriptMetadata => {\n return {\n ...metadata,\n status: metadata.status ?? 'reviewed',\n history: metadata.history ?? [],\n tasks: metadata.tasks ?? [],\n };\n};\n\n/**\n * Complete a task by ID\n */\nexport const completeTask = (metadata: TranscriptMetadata, taskId: string): TranscriptMetadata => {\n const tasks = metadata.tasks || [];\n const taskIndex = tasks.findIndex(t => t.id === taskId);\n \n if (taskIndex === -1) {\n throw new Error(`Task not found: ${taskId}`);\n }\n \n const now = new Date().toISOString();\n const updatedTasks = [...tasks];\n updatedTasks[taskIndex] = {\n ...updatedTasks[taskIndex],\n status: 'done',\n changed: now,\n completed: now,\n };\n \n return {\n ...metadata,\n tasks: updatedTasks,\n };\n};\n\n/**\n * Delete a task by ID\n */\nexport const deleteTask = (metadata: TranscriptMetadata, taskId: string): TranscriptMetadata => {\n const tasks = metadata.tasks || [];\n const taskIndex = tasks.findIndex(t => t.id === taskId);\n \n if (taskIndex === -1) {\n throw new Error(`Task not found: ${taskId}`);\n }\n \n return {\n ...metadata,\n tasks: tasks.filter(t => t.id !== taskId),\n };\n};\n\n/**\n * Add a task to metadata\n */\nexport const addTask = (metadata: TranscriptMetadata, description: string): { metadata: TranscriptMetadata; task: Task } => {\n const task = createTask(description);\n \n return {\n metadata: {\n ...metadata,\n tasks: [...(metadata.tasks || []), task],\n },\n task,\n };\n};\n\n\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index14.js","sources":["../src/util/media.ts"],"sourcesContent":["import ffmpeg from 'fluent-ffmpeg';\nimport { Logger } from 'winston';\nimport path from 'node:path';\nimport * as Storage from '@/util/storage';\n\nexport interface Media {\n getAudioCreationTime: (filePath: string) => Promise<Date | null>;\n getFileSize: (filePath: string) => Promise<number>;\n splitAudioFile: (filePath: string, outputDir: string, maxSizeBytes: number) => Promise<string[]>;\n convertToSupportedFormat: (filePath: string, outputDir: string, forceConversion?: boolean) => Promise<string>;\n}\n\nconst ffprobeAsync = (filePath: string): Promise<any> => {\n return new Promise((resolve, reject) => {\n ffmpeg.ffprobe(filePath, (err, metadata) => {\n if (err) return reject(err);\n resolve(metadata);\n });\n });\n};\n\n\nexport const create = (logger: Logger): Media => {\n const storage = Storage.create({ log: logger.debug });\n\n // Extract creation time from audio file using ffmpeg\n const getAudioCreationTime = async (filePath: string): Promise<Date | null> => {\n try {\n const metadata = await ffprobeAsync(filePath);\n\n // Look for creation_time in format tags\n const formatTags = metadata?.format?.tags;\n if (formatTags?.creation_time) {\n logger.debug('Found creation_time in format tags: %s', formatTags.creation_time);\n return new Date(formatTags.creation_time);\n }\n\n // Check for creation_time in stream tags as fallback\n if (metadata?.streams?.length > 0) {\n for (const stream of metadata.streams) {\n if (stream.tags?.creation_time) {\n logger.debug('Found creation_time in stream tags: %s', stream.tags.creation_time);\n return new Date(stream.tags.creation_time);\n }\n }\n }\n\n logger.debug('No creation_time found in audio file metadata');\n return null;\n } catch (error) {\n logger.error('Error extracting creation time from audio file: %s', error);\n return null;\n }\n };\n\n // Get file size in bytes\n const getFileSize = async (filePath: string): Promise<number> => {\n try {\n return await storage.getFileSize(filePath);\n } catch (error) {\n logger.error('Error getting file size: %s', error);\n throw new Error(`Failed to get file size for ${filePath}: ${error}`);\n }\n };\n\n // Split large audio file into smaller chunks\n const splitAudioFile = async (filePath: string, outputDir: string, maxSizeBytes: number): Promise<string[]> => {\n try {\n const metadata = await ffprobeAsync(filePath);\n const duration = parseFloat(metadata.format.duration);\n\n // Calculate how many segments we need based on file size and max size\n const fileSize = await getFileSize(filePath);\n const segmentCount = Math.ceil(fileSize / maxSizeBytes);\n\n // Calculate segment duration\n const segmentDuration = duration / segmentCount;\n logger.debug(`Splitting ${filePath} (${fileSize} bytes) into ${segmentCount} segments of ~${segmentDuration} seconds each`);\n\n // Create output directory if it doesn't exist\n await storage.createDirectory(outputDir);\n\n const outputFiles: string[] = [];\n const fileExt = path.extname(filePath);\n const fileName = path.basename(filePath, fileExt);\n\n // Create a promise for each segment\n const promises = [];\n\n for (let i = 0; i < segmentCount; i++) {\n const startTime = i * segmentDuration;\n const outputPath = path.join(outputDir, `${fileName}_part${i + 1}${fileExt}`);\n outputFiles.push(outputPath);\n\n const promise = new Promise<void>((resolve, reject) => {\n ffmpeg(filePath)\n .setStartTime(startTime)\n .setDuration(segmentDuration)\n .output(outputPath)\n .on('end', () => {\n logger.debug(`Created segment ${i + 1}/${segmentCount}: ${outputPath}`);\n resolve();\n })\n .on('error', (err) => {\n logger.error(`Error creating segment ${i + 1}/${segmentCount}: ${err}`);\n reject(err);\n })\n .run();\n });\n\n promises.push(promise);\n }\n\n // Wait for all segments to be created\n await Promise.all(promises);\n return outputFiles;\n } catch (error) {\n logger.error('Error splitting audio file: %s', error);\n throw new Error(`Failed to split audio file ${filePath}: ${error}`);\n }\n };\n\n // Convert audio file to a format supported by OpenAI Whisper API\n // Supported formats: flac, m4a, mp3, mp4, mpeg, mpga, oga, ogg, wav, webm\n const convertToSupportedFormat = async (filePath: string, outputDir: string, forceConversion = false): Promise<string> => {\n try {\n const fileExt = path.extname(filePath).toLowerCase();\n\n // List of formats that OpenAI supports\n const supportedFormats = ['.flac', '.m4a', '.mp3', '.mp4', '.mpeg', '.mpga', '.oga', '.ogg', '.wav', '.webm'];\n\n // If already in a supported format and not forcing conversion, return as-is\n if (supportedFormats.includes(fileExt) && !forceConversion) {\n logger.debug(`File ${filePath} is already in a supported format: ${fileExt}`);\n return filePath;\n }\n\n // If forcing conversion and already MP3, return as-is (MP3 is already compressed)\n if (forceConversion && fileExt === '.mp3') {\n logger.debug(`File ${filePath} is already MP3 (compressed format)`);\n return filePath;\n }\n\n // Otherwise, convert to mp3 (widely supported and good compression)\n logger.info(`Converting ${fileExt} file to mp3 for transcription...`);\n const fileName = path.basename(filePath, fileExt);\n const outputPath = path.join(outputDir, `${fileName}.mp3`);\n\n // Check if converted file already exists\n if (await storage.exists(outputPath)) {\n logger.debug(`Converted file already exists: ${outputPath}`);\n return outputPath;\n }\n\n // Create output directory if it doesn't exist\n await storage.createDirectory(outputDir);\n\n return new Promise<string>((resolve, reject) => {\n ffmpeg(filePath)\n .toFormat('mp3')\n .audioBitrate('128k')\n .output(outputPath)\n .on('end', () => {\n logger.info(`Successfully converted to: ${outputPath}`);\n resolve(outputPath);\n })\n .on('error', (err) => {\n logger.error(`Error converting audio file: ${err}`);\n reject(new Error(`Failed to convert ${filePath} to mp3: ${err.message}`));\n })\n .run();\n });\n } catch (error) {\n logger.error('Error in convertToSupportedFormat: %s', error);\n throw new Error(`Failed to convert audio file ${filePath}: ${error}`);\n }\n };\n\n return {\n getAudioCreationTime,\n getFileSize,\n splitAudioFile,\n convertToSupportedFormat,\n }\n}\n"],"names":["storage","Storage.create","path"],"mappings":"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"}