@redaksjon/protokoll-engine 0.1.1-dev.0
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/README.md +47 -0
- package/dist/agentic/executor.d.ts +21 -0
- package/dist/agentic/executor.d.ts.map +1 -0
- package/dist/agentic/index.d.ts +27 -0
- package/dist/agentic/index.d.ts.map +1 -0
- package/dist/agentic/registry.d.ts +11 -0
- package/dist/agentic/registry.d.ts.map +1 -0
- package/dist/agentic/tools/lookup-person.d.ts +3 -0
- package/dist/agentic/tools/lookup-person.d.ts.map +1 -0
- package/dist/agentic/tools/lookup-project.d.ts +3 -0
- package/dist/agentic/tools/lookup-project.d.ts.map +1 -0
- package/dist/agentic/tools/route-note.d.ts +3 -0
- package/dist/agentic/tools/route-note.d.ts.map +1 -0
- package/dist/agentic/tools/store-context.d.ts +3 -0
- package/dist/agentic/tools/store-context.d.ts.map +1 -0
- package/dist/agentic/tools/verify-spelling.d.ts +3 -0
- package/dist/agentic/tools/verify-spelling.d.ts.map +1 -0
- package/dist/agentic/types.d.ts +110 -0
- package/dist/agentic/types.d.ts.map +1 -0
- package/dist/constants.d.ts +98 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/feedback/analyzer.d.ts +13 -0
- package/dist/feedback/analyzer.d.ts.map +1 -0
- package/dist/feedback/decision-tracker.d.ts +14 -0
- package/dist/feedback/decision-tracker.d.ts.map +1 -0
- package/dist/feedback/handler.d.ts +14 -0
- package/dist/feedback/handler.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +12 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/types.d.ts +72 -0
- package/dist/feedback/types.d.ts.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/index10.js +4 -0
- package/dist/index10.js.map +1 -0
- package/dist/index11.js +22 -0
- package/dist/index11.js.map +1 -0
- package/dist/index12.js +125 -0
- package/dist/index12.js.map +1 -0
- package/dist/index13.js +124 -0
- package/dist/index13.js.map +1 -0
- package/dist/index14.js +296 -0
- package/dist/index14.js.map +1 -0
- package/dist/index15.js +100 -0
- package/dist/index15.js.map +1 -0
- package/dist/index16.js +107 -0
- package/dist/index16.js.map +1 -0
- package/dist/index17.js +185 -0
- package/dist/index17.js.map +1 -0
- package/dist/index18.js +53 -0
- package/dist/index18.js.map +1 -0
- package/dist/index19.js +19 -0
- package/dist/index19.js.map +1 -0
- package/dist/index2.js +33 -0
- package/dist/index2.js.map +1 -0
- package/dist/index20.js +105 -0
- package/dist/index20.js.map +1 -0
- package/dist/index21.js +26 -0
- package/dist/index21.js.map +1 -0
- package/dist/index22.js +49 -0
- package/dist/index22.js.map +1 -0
- package/dist/index23.js +119 -0
- package/dist/index23.js.map +1 -0
- package/dist/index24.js +330 -0
- package/dist/index24.js.map +1 -0
- package/dist/index25.js +57 -0
- package/dist/index25.js.map +1 -0
- package/dist/index26.js +38 -0
- package/dist/index26.js.map +1 -0
- package/dist/index27.js +127 -0
- package/dist/index27.js.map +1 -0
- package/dist/index28.js +157 -0
- package/dist/index28.js.map +1 -0
- package/dist/index29.js +163 -0
- package/dist/index29.js.map +1 -0
- package/dist/index3.js +36 -0
- package/dist/index3.js.map +1 -0
- package/dist/index30.js +173 -0
- package/dist/index30.js.map +1 -0
- package/dist/index31.js +423 -0
- package/dist/index31.js.map +1 -0
- package/dist/index32.js +161 -0
- package/dist/index32.js.map +1 -0
- package/dist/index33.js +152 -0
- package/dist/index33.js.map +1 -0
- package/dist/index34.js +56 -0
- package/dist/index34.js.map +1 -0
- package/dist/index35.js +103 -0
- package/dist/index35.js.map +1 -0
- package/dist/index36.js +451 -0
- package/dist/index36.js.map +1 -0
- package/dist/index37.js +431 -0
- package/dist/index37.js.map +1 -0
- package/dist/index38.js +87 -0
- package/dist/index38.js.map +1 -0
- package/dist/index39.js +122 -0
- package/dist/index39.js.map +1 -0
- package/dist/index4.js +3 -0
- package/dist/index4.js.map +1 -0
- package/dist/index40.js +299 -0
- package/dist/index40.js.map +1 -0
- package/dist/index41.js +49 -0
- package/dist/index41.js.map +1 -0
- package/dist/index42.js +151 -0
- package/dist/index42.js.map +1 -0
- package/dist/index43.js +226 -0
- package/dist/index43.js.map +1 -0
- package/dist/index44.js +49 -0
- package/dist/index44.js.map +1 -0
- package/dist/index45.js +45 -0
- package/dist/index45.js.map +1 -0
- package/dist/index46.js +37 -0
- package/dist/index46.js.map +1 -0
- package/dist/index47.js +51 -0
- package/dist/index47.js.map +1 -0
- package/dist/index48.js +39 -0
- package/dist/index48.js.map +1 -0
- package/dist/index49.js +239 -0
- package/dist/index49.js.map +1 -0
- package/dist/index5.js +17 -0
- package/dist/index5.js.map +1 -0
- package/dist/index50.js +163 -0
- package/dist/index50.js.map +1 -0
- package/dist/index51.js +81 -0
- package/dist/index51.js.map +1 -0
- package/dist/index52.js +78 -0
- package/dist/index52.js.map +1 -0
- package/dist/index53.js +22 -0
- package/dist/index53.js.map +1 -0
- package/dist/index54.js +8 -0
- package/dist/index54.js.map +1 -0
- package/dist/index55.js +8 -0
- package/dist/index55.js.map +1 -0
- package/dist/index56.js +17 -0
- package/dist/index56.js.map +1 -0
- package/dist/index57.js +4 -0
- package/dist/index57.js.map +1 -0
- package/dist/index58.js +17 -0
- package/dist/index58.js.map +1 -0
- package/dist/index59.js +4 -0
- package/dist/index59.js.map +1 -0
- package/dist/index6.js +22 -0
- package/dist/index6.js.map +1 -0
- package/dist/index60.js +6 -0
- package/dist/index60.js.map +1 -0
- package/dist/index7.js +27 -0
- package/dist/index7.js.map +1 -0
- package/dist/index8.js +22 -0
- package/dist/index8.js.map +1 -0
- package/dist/index9.js +5 -0
- package/dist/index9.js.map +1 -0
- package/dist/logging.d.ts +7 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/output/index.d.ts +15 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/phases/complete.d.ts +17 -0
- package/dist/phases/complete.d.ts.map +1 -0
- package/dist/phases/index.d.ts +5 -0
- package/dist/phases/index.d.ts.map +1 -0
- package/dist/phases/locate.d.ts +15 -0
- package/dist/phases/locate.d.ts.map +1 -0
- package/dist/phases/simple-replace.d.ts +72 -0
- package/dist/phases/simple-replace.d.ts.map +1 -0
- package/dist/phases/transcribe.d.ts +19 -0
- package/dist/phases/transcribe.d.ts.map +1 -0
- package/dist/pipeline/index.d.ts +10 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/orchestrator.d.ts +13 -0
- package/dist/pipeline/orchestrator.d.ts.map +1 -0
- package/dist/pipeline/types.d.ts +58 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/prompt/index.d.ts +3 -0
- package/dist/prompt/index.d.ts.map +1 -0
- package/dist/prompt/templates.d.ts +40 -0
- package/dist/prompt/templates.d.ts.map +1 -0
- package/dist/prompt/transcribe.d.ts +42 -0
- package/dist/prompt/transcribe.d.ts.map +1 -0
- package/dist/reasoning/client.d.ts +42 -0
- package/dist/reasoning/client.d.ts.map +1 -0
- package/dist/reasoning/index.d.ts +17 -0
- package/dist/reasoning/index.d.ts.map +1 -0
- package/dist/reasoning/strategy.d.ts +12 -0
- package/dist/reasoning/strategy.d.ts.map +1 -0
- package/dist/reasoning/types.d.ts +58 -0
- package/dist/reasoning/types.d.ts.map +1 -0
- package/dist/reflection/collector.d.ts +18 -0
- package/dist/reflection/collector.d.ts.map +1 -0
- package/dist/reflection/index.d.ts +13 -0
- package/dist/reflection/index.d.ts.map +1 -0
- package/dist/reflection/reporter.d.ts +10 -0
- package/dist/reflection/reporter.d.ts.map +1 -0
- package/dist/reflection/types.d.ts +99 -0
- package/dist/reflection/types.d.ts.map +1 -0
- package/dist/routing/classifier.d.ts +8 -0
- package/dist/routing/classifier.d.ts.map +1 -0
- package/dist/routing/index.d.ts +12 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/router.d.ts +8 -0
- package/dist/routing/router.d.ts.map +1 -0
- package/dist/routing/types.d.ts +68 -0
- package/dist/routing/types.d.ts.map +1 -0
- package/dist/transcript/feedback.d.ts +70 -0
- package/dist/transcript/feedback.d.ts.map +1 -0
- package/dist/transcript/index.d.ts +10 -0
- package/dist/transcript/index.d.ts.map +1 -0
- package/dist/transcript/operations.d.ts +152 -0
- package/dist/transcript/operations.d.ts.map +1 -0
- package/dist/transcript/pkl-utils.d.ts +66 -0
- package/dist/transcript/pkl-utils.d.ts.map +1 -0
- package/dist/transcription/index.d.ts +17 -0
- package/dist/transcription/index.d.ts.map +1 -0
- package/dist/transcription/service.d.ts +10 -0
- package/dist/transcription/service.d.ts.map +1 -0
- package/dist/transcription/types.d.ts +41 -0
- package/dist/transcription/types.d.ts.map +1 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/util/collision-detector.d.ts +77 -0
- package/dist/util/collision-detector.d.ts.map +1 -0
- package/dist/util/dates.d.ts +57 -0
- package/dist/util/dates.d.ts.map +1 -0
- package/dist/util/general.d.ts +3 -0
- package/dist/util/general.d.ts.map +1 -0
- package/dist/util/media.d.ts +9 -0
- package/dist/util/media.d.ts.map +1 -0
- package/dist/util/metadata.d.ts +138 -0
- package/dist/util/metadata.d.ts.map +1 -0
- package/dist/util/openai.d.ts +22 -0
- package/dist/util/openai.d.ts.map +1 -0
- package/dist/util/sounds-like-database.d.ts +98 -0
- package/dist/util/sounds-like-database.d.ts.map +1 -0
- package/dist/util/storage.d.ts +35 -0
- package/dist/util/storage.d.ts.map +1 -0
- package/dist/util/text-replacer.d.ts +56 -0
- package/dist/util/text-replacer.d.ts.map +1 -0
- package/dist/utils/entityFinder.d.ts +29 -0
- package/dist/utils/entityFinder.d.ts.map +1 -0
- package/package.json +84 -0
package/dist/index27.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
|
|
4
|
+
const create = (config, classifier) => {
|
|
5
|
+
const route = (context) => {
|
|
6
|
+
const results = classifier.classify(context, config.projects);
|
|
7
|
+
if (results.length === 0) {
|
|
8
|
+
return {
|
|
9
|
+
projectId: null,
|
|
10
|
+
destination: config.default,
|
|
11
|
+
confidence: 1,
|
|
12
|
+
signals: [],
|
|
13
|
+
reasoning: "No project matches found, using default routing"
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const bestMatch = results[0];
|
|
17
|
+
const matchedProject = config.projects.find((p) => p.projectId === bestMatch.projectId);
|
|
18
|
+
const highConfidenceMatches = results.filter((r) => r.confidence > 0.5);
|
|
19
|
+
if (highConfidenceMatches.length > 1 && config.conflict_resolution !== "primary") {
|
|
20
|
+
return {
|
|
21
|
+
projectId: bestMatch.projectId,
|
|
22
|
+
destination: matchedProject.destination,
|
|
23
|
+
confidence: bestMatch.confidence,
|
|
24
|
+
signals: bestMatch.signals,
|
|
25
|
+
reasoning: bestMatch.reasoning,
|
|
26
|
+
auto_tags: matchedProject.auto_tags,
|
|
27
|
+
alternateMatches: highConfidenceMatches.slice(1)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
projectId: bestMatch.projectId,
|
|
32
|
+
destination: matchedProject.destination,
|
|
33
|
+
confidence: bestMatch.confidence,
|
|
34
|
+
signals: bestMatch.signals,
|
|
35
|
+
reasoning: bestMatch.reasoning,
|
|
36
|
+
auto_tags: matchedProject.auto_tags
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
const buildOutputPath = (decision, context) => {
|
|
40
|
+
const { destination } = decision;
|
|
41
|
+
const basePath = expandPath(destination.path);
|
|
42
|
+
const directoryPath = buildDirectoryPath(basePath, destination.structure, context.audioDate);
|
|
43
|
+
const filename = buildFilename(destination.filename_options, context, destination.structure);
|
|
44
|
+
return path.join(directoryPath, filename + ".md");
|
|
45
|
+
};
|
|
46
|
+
return { route, buildOutputPath };
|
|
47
|
+
};
|
|
48
|
+
function buildDirectoryPath(basePath, structure, date) {
|
|
49
|
+
const year = date.getFullYear().toString();
|
|
50
|
+
const month = (date.getMonth() + 1).toString();
|
|
51
|
+
const day = date.getDate().toString();
|
|
52
|
+
switch (structure) {
|
|
53
|
+
case "none":
|
|
54
|
+
return basePath;
|
|
55
|
+
case "year":
|
|
56
|
+
return path.join(basePath, year);
|
|
57
|
+
case "month":
|
|
58
|
+
return path.join(basePath, year, month);
|
|
59
|
+
case "day":
|
|
60
|
+
return path.join(basePath, year, month, day);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function buildFilename(options, context, structure) {
|
|
64
|
+
const parts = [];
|
|
65
|
+
const date = context.audioDate;
|
|
66
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
67
|
+
for (const option of options) {
|
|
68
|
+
switch (option) {
|
|
69
|
+
case "date": {
|
|
70
|
+
const day = pad(date.getDate());
|
|
71
|
+
const month = pad(date.getMonth() + 1);
|
|
72
|
+
const year = date.getFullYear().toString().slice(2);
|
|
73
|
+
switch (structure) {
|
|
74
|
+
case "day":
|
|
75
|
+
break;
|
|
76
|
+
case "month":
|
|
77
|
+
parts.push(day);
|
|
78
|
+
break;
|
|
79
|
+
case "year":
|
|
80
|
+
parts.push(`${month}-${day}`);
|
|
81
|
+
break;
|
|
82
|
+
case "none":
|
|
83
|
+
parts.push(`${year}${month}${day}`);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case "time": {
|
|
89
|
+
const hours = pad(date.getHours());
|
|
90
|
+
const minutes = pad(date.getMinutes());
|
|
91
|
+
parts.push(`${hours}${minutes}`);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "subject": {
|
|
95
|
+
const subject = extractSubject(context.transcriptText, context.sourceFile, context.subjectOverride);
|
|
96
|
+
if (subject) {
|
|
97
|
+
parts.push(subject);
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return parts.join("-").replace(/--+/g, "-");
|
|
104
|
+
}
|
|
105
|
+
function extractSubject(text, sourceFile, subjectOverride) {
|
|
106
|
+
if (subjectOverride && subjectOverride.length > 0) {
|
|
107
|
+
return slugify(subjectOverride);
|
|
108
|
+
}
|
|
109
|
+
const firstSentence = text.split(/[.!?]/)[0]?.trim() ?? "";
|
|
110
|
+
const cleaned = firstSentence.replace(/^(this is a note about|note about|regarding|re:|meeting notes?:?)/i, "").trim();
|
|
111
|
+
if (cleaned.length > 3 && cleaned.length < 50) {
|
|
112
|
+
return slugify(cleaned);
|
|
113
|
+
}
|
|
114
|
+
return path.basename(sourceFile, path.extname(sourceFile)).replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
|
|
115
|
+
}
|
|
116
|
+
function slugify(text) {
|
|
117
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/--+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
118
|
+
}
|
|
119
|
+
function expandPath(p) {
|
|
120
|
+
if (p.startsWith("~")) {
|
|
121
|
+
return path.join(os.homedir(), p.slice(1));
|
|
122
|
+
}
|
|
123
|
+
return p;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { create };
|
|
127
|
+
//# sourceMappingURL=index27.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index27.js","sources":["../src/routing/router.ts"],"sourcesContent":["/**\n * Router\n * \n * Handles routing decisions and path building using Dreadcabinet patterns.\n * Takes classification results and builds output paths with appropriate\n * directory structure and filenames.\n * \n * Design Note: This module is designed to be self-contained and may be\n * extracted for use in other tools (kronologi, observasjon) in the future.\n */\n\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { \n RoutingContext, \n RouteDecision,\n RoutingConfig,\n FilesystemStructure\n} from './types';\nimport * as Classifier from './classifier';\n\nexport interface RouterInstance {\n route(context: RoutingContext): RouteDecision;\n buildOutputPath(decision: RouteDecision, context: RoutingContext): string;\n}\n\nexport const create = (\n config: RoutingConfig,\n classifier: Classifier.ClassifierInstance\n): RouterInstance => {\n \n const route = (context: RoutingContext): RouteDecision => {\n const results = classifier.classify(context, config.projects);\n \n if (results.length === 0) {\n return {\n projectId: null,\n destination: config.default,\n confidence: 1.0,\n signals: [],\n reasoning: 'No project matches found, using default routing',\n };\n }\n \n const bestMatch = results[0];\n const matchedProject = config.projects.find(p => p.projectId === bestMatch.projectId)!;\n \n // Handle conflict resolution if multiple high-confidence matches\n const highConfidenceMatches = results.filter(r => r.confidence > 0.5);\n \n if (highConfidenceMatches.length > 1 && config.conflict_resolution !== 'primary') {\n // Return best with alternates noted\n return {\n projectId: bestMatch.projectId,\n destination: matchedProject.destination,\n confidence: bestMatch.confidence,\n signals: bestMatch.signals,\n reasoning: bestMatch.reasoning,\n auto_tags: matchedProject.auto_tags,\n alternateMatches: highConfidenceMatches.slice(1),\n };\n }\n \n return {\n projectId: bestMatch.projectId,\n destination: matchedProject.destination,\n confidence: bestMatch.confidence,\n signals: bestMatch.signals,\n reasoning: bestMatch.reasoning,\n auto_tags: matchedProject.auto_tags,\n };\n };\n \n const buildOutputPath = (decision: RouteDecision, context: RoutingContext): string => {\n const { destination } = decision;\n \n // Expand ~ to home directory\n const basePath = expandPath(destination.path);\n \n // Build directory structure using Dreadcabinet patterns\n const directoryPath = buildDirectoryPath(basePath, destination.structure, context.audioDate);\n \n // Build filename using Dreadcabinet patterns\n // Pass structure so filename doesn't repeat info already in path\n const filename = buildFilename(destination.filename_options, context, destination.structure);\n \n return path.join(directoryPath, filename + '.md');\n };\n \n return { route, buildOutputPath };\n};\n\n// Dreadcabinet-style directory building\nfunction buildDirectoryPath(\n basePath: string, \n structure: FilesystemStructure, \n date: Date\n): string {\n const year = date.getFullYear().toString();\n const month = (date.getMonth() + 1).toString();\n const day = date.getDate().toString();\n \n switch (structure) {\n case 'none':\n return basePath;\n case 'year':\n return path.join(basePath, year);\n case 'month':\n return path.join(basePath, year, month);\n case 'day':\n return path.join(basePath, year, month, day);\n }\n}\n\n// Dreadcabinet-style filename building\n// The date portion is adjusted based on what's already in the directory path\nfunction buildFilename(\n options: Array<'date' | 'time' | 'subject'>,\n context: RoutingContext,\n structure: FilesystemStructure\n): string {\n const parts: string[] = [];\n const date = context.audioDate;\n const pad = (n: number) => n.toString().padStart(2, '0');\n \n for (const option of options) {\n switch (option) {\n case 'date': {\n // Adjust date format based on directory structure\n // Don't repeat info already in the path\n const day = pad(date.getDate());\n const month = pad(date.getMonth() + 1);\n const year = date.getFullYear().toString().slice(2);\n \n switch (structure) {\n case 'day':\n // Path has year/month/day - no date needed in filename\n break;\n case 'month':\n // Path has year/month - only day in filename\n parts.push(day);\n break;\n case 'year':\n // Path has year - month+day in filename\n parts.push(`${month}-${day}`);\n break;\n case 'none':\n // No date in path - full date in filename (YYMMDD)\n parts.push(`${year}${month}${day}`);\n break;\n }\n break;\n }\n case 'time': {\n const hours = pad(date.getHours());\n const minutes = pad(date.getMinutes());\n parts.push(`${hours}${minutes}`);\n break;\n }\n case 'subject': {\n const subject = extractSubject(context.transcriptText, context.sourceFile, context.subjectOverride);\n if (subject) {\n parts.push(subject);\n }\n break;\n }\n }\n }\n \n // Join and clean up any double dashes\n return parts.join('-').replace(/--+/g, '-');\n}\n\nfunction extractSubject(text: string, sourceFile: string, subjectOverride?: string): string {\n // Use override if provided (e.g., LLM-generated title)\n if (subjectOverride && subjectOverride.length > 0) {\n return slugify(subjectOverride);\n }\n \n // Try to extract from first sentence\n const firstSentence = text.split(/[.!?]/)[0]?.trim() ?? '';\n \n // Remove common prefixes\n const cleaned = firstSentence\n .replace(/^(this is a note about|note about|regarding|re:|meeting notes?:?)/i, '')\n .trim();\n \n if (cleaned.length > 3 && cleaned.length < 50) {\n return slugify(cleaned);\n }\n \n // Fall back to source filename\n return path.basename(sourceFile, path.extname(sourceFile))\n .replace(/[^a-zA-Z0-9-]/g, '-')\n .toLowerCase();\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with dash\n .replace(/--+/g, '-') // Collapse multiple dashes\n .replace(/^-|-$/g, '') // Remove leading/trailing dashes\n .slice(0, 40);\n}\n\nfunction expandPath(p: string): string {\n if (p.startsWith('~')) {\n return path.join(os.homedir(), p.slice(1));\n }\n return p;\n}\n\n"],"names":[],"mappings":"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"}
|
package/dist/index28.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const create = (contextInstance) => {
|
|
2
|
+
const classify = (routingContext, routes) => {
|
|
3
|
+
const results = [];
|
|
4
|
+
const normalizedText = routingContext.transcriptText.toLowerCase();
|
|
5
|
+
for (const route of routes) {
|
|
6
|
+
if (route.active === false) continue;
|
|
7
|
+
const signals = [];
|
|
8
|
+
const classification = route.classification;
|
|
9
|
+
for (const phrase of classification.explicit_phrases ?? []) {
|
|
10
|
+
if (normalizedText.includes(phrase.toLowerCase())) {
|
|
11
|
+
signals.push({
|
|
12
|
+
type: "explicit_phrase",
|
|
13
|
+
value: phrase,
|
|
14
|
+
weight: 0.9
|
|
15
|
+
// High confidence
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const peopleInText = routingContext.detectedPeople ?? detectPeopleFromContext(normalizedText, contextInstance);
|
|
20
|
+
for (const personId of classification.associated_people ?? []) {
|
|
21
|
+
if (peopleInText.includes(personId)) {
|
|
22
|
+
const person = contextInstance.getPerson(personId);
|
|
23
|
+
signals.push({
|
|
24
|
+
type: "associated_person",
|
|
25
|
+
value: person?.name ?? personId,
|
|
26
|
+
weight: 0.6
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const companiesInText = routingContext.detectedCompanies ?? detectCompaniesFromContext(normalizedText, contextInstance);
|
|
31
|
+
for (const companyId of classification.associated_companies ?? []) {
|
|
32
|
+
if (companiesInText.includes(companyId)) {
|
|
33
|
+
const company = contextInstance.getCompany(companyId);
|
|
34
|
+
signals.push({
|
|
35
|
+
type: "associated_company",
|
|
36
|
+
value: company?.name ?? companyId,
|
|
37
|
+
weight: 0.5
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const topic of classification.topics ?? []) {
|
|
42
|
+
if (normalizedText.includes(topic.toLowerCase())) {
|
|
43
|
+
signals.push({
|
|
44
|
+
type: "topic",
|
|
45
|
+
value: topic,
|
|
46
|
+
weight: 0.3
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const inferredContextType = inferContextType(normalizedText);
|
|
51
|
+
if (inferredContextType === classification.context_type) {
|
|
52
|
+
signals.push({
|
|
53
|
+
type: "context_type",
|
|
54
|
+
value: classification.context_type,
|
|
55
|
+
weight: 0.2
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (signals.length > 0) {
|
|
59
|
+
const confidence = calculateConfidence(signals);
|
|
60
|
+
results.push({
|
|
61
|
+
projectId: route.projectId,
|
|
62
|
+
confidence,
|
|
63
|
+
signals,
|
|
64
|
+
reasoning: buildReasoning(signals)
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return results.sort((a, b) => b.confidence - a.confidence);
|
|
69
|
+
};
|
|
70
|
+
const calculateConfidence = (signals) => {
|
|
71
|
+
if (signals.length === 0) return 0;
|
|
72
|
+
let totalWeight = 0;
|
|
73
|
+
let weightedSum = 0;
|
|
74
|
+
for (let i = 0; i < signals.length; i++) {
|
|
75
|
+
const signal = signals[i];
|
|
76
|
+
const positionFactor = 1 / (1 + i * 0.3);
|
|
77
|
+
const effectiveWeight = signal.weight * positionFactor;
|
|
78
|
+
weightedSum += effectiveWeight;
|
|
79
|
+
totalWeight += positionFactor;
|
|
80
|
+
}
|
|
81
|
+
return Math.min(weightedSum / Math.max(totalWeight, 1), 0.99);
|
|
82
|
+
};
|
|
83
|
+
const buildReasoning = (signals) => {
|
|
84
|
+
const parts = signals.map((s) => {
|
|
85
|
+
switch (s.type) {
|
|
86
|
+
case "explicit_phrase":
|
|
87
|
+
return `explicit phrase: "${s.value}"`;
|
|
88
|
+
case "associated_person":
|
|
89
|
+
return `mentioned ${s.value} (associated)`;
|
|
90
|
+
case "associated_company":
|
|
91
|
+
return `mentioned ${s.value} (associated company)`;
|
|
92
|
+
case "topic":
|
|
93
|
+
return `topic: ${s.value}`;
|
|
94
|
+
case "context_type":
|
|
95
|
+
return `context: ${s.value}`;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return parts.join(", ");
|
|
99
|
+
};
|
|
100
|
+
return { classify, calculateConfidence };
|
|
101
|
+
};
|
|
102
|
+
function detectPeopleFromContext(text, context) {
|
|
103
|
+
const found = [];
|
|
104
|
+
for (const person of context.getAllPeople()) {
|
|
105
|
+
const nameNormalized = person.name.toLowerCase();
|
|
106
|
+
if (text.includes(nameNormalized)) {
|
|
107
|
+
found.push(person.id);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
for (const variant of person.sounds_like ?? []) {
|
|
111
|
+
if (text.includes(variant.toLowerCase())) {
|
|
112
|
+
found.push(person.id);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return found;
|
|
118
|
+
}
|
|
119
|
+
function detectCompaniesFromContext(text, context) {
|
|
120
|
+
const found = [];
|
|
121
|
+
for (const company of context.getAllCompanies()) {
|
|
122
|
+
const nameNormalized = company.name.toLowerCase();
|
|
123
|
+
if (text.includes(nameNormalized)) {
|
|
124
|
+
found.push(company.id);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (company.fullName && text.includes(company.fullName.toLowerCase())) {
|
|
128
|
+
found.push(company.id);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
for (const variant of company.sounds_like ?? []) {
|
|
132
|
+
if (text.includes(variant.toLowerCase())) {
|
|
133
|
+
found.push(company.id);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return found;
|
|
139
|
+
}
|
|
140
|
+
function inferContextType(text) {
|
|
141
|
+
const workIndicators = ["meeting", "project", "deadline", "team", "client", "report"];
|
|
142
|
+
const personalIndicators = ["family", "weekend", "vacation", "hobby", "friend"];
|
|
143
|
+
let workScore = 0;
|
|
144
|
+
let personalScore = 0;
|
|
145
|
+
for (const word of workIndicators) {
|
|
146
|
+
if (text.includes(word)) workScore++;
|
|
147
|
+
}
|
|
148
|
+
for (const word of personalIndicators) {
|
|
149
|
+
if (text.includes(word)) personalScore++;
|
|
150
|
+
}
|
|
151
|
+
if (workScore > personalScore + 1) return "work";
|
|
152
|
+
if (personalScore > workScore + 1) return "personal";
|
|
153
|
+
return "mixed";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export { create };
|
|
157
|
+
//# sourceMappingURL=index28.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index28.js","sources":["../src/routing/classifier.ts"],"sourcesContent":["/**\n * Intelligent Classifier\n * \n * Multi-signal classification system for routing transcripts to projects.\n * Uses various signals (explicit phrases, associated people/companies, topics)\n * to determine the best project match with confidence scoring.\n * \n * Design Note: This module is designed to be self-contained and may be\n * extracted for use in other tools (kronologi, observasjon) in the future.\n */\n\nimport { \n ClassificationResult, \n ClassificationSignal, \n ProjectRoute, \n RoutingContext \n} from './types';\nimport * as Context from '@redaksjon/context';\n\nexport interface ClassifierInstance {\n classify(context: RoutingContext, routes: ProjectRoute[]): ClassificationResult[];\n calculateConfidence(signals: ClassificationSignal[]): number;\n}\n\nexport const create = (contextInstance: Context.ContextInstance): ClassifierInstance => {\n \n const classify = (\n routingContext: RoutingContext, \n routes: ProjectRoute[]\n ): ClassificationResult[] => {\n const results: ClassificationResult[] = [];\n const normalizedText = routingContext.transcriptText.toLowerCase();\n \n for (const route of routes) {\n if (route.active === false) continue;\n \n const signals: ClassificationSignal[] = [];\n const classification = route.classification;\n \n // 1. Check explicit phrases (highest weight)\n for (const phrase of classification.explicit_phrases ?? []) {\n if (normalizedText.includes(phrase.toLowerCase())) {\n signals.push({\n type: 'explicit_phrase',\n value: phrase,\n weight: 0.9, // High confidence\n });\n }\n }\n \n // 2. Check associated people\n const peopleInText = routingContext.detectedPeople ?? \n detectPeopleFromContext(normalizedText, contextInstance);\n \n for (const personId of classification.associated_people ?? []) {\n if (peopleInText.includes(personId)) {\n const person = contextInstance.getPerson(personId);\n signals.push({\n type: 'associated_person',\n value: person?.name ?? personId,\n weight: 0.6,\n });\n }\n }\n \n // 3. Check associated companies\n const companiesInText = routingContext.detectedCompanies ?? \n detectCompaniesFromContext(normalizedText, contextInstance);\n \n for (const companyId of classification.associated_companies ?? []) {\n if (companiesInText.includes(companyId)) {\n const company = contextInstance.getCompany(companyId);\n signals.push({\n type: 'associated_company',\n value: company?.name ?? companyId,\n weight: 0.5,\n });\n }\n }\n \n // 4. Check topics\n for (const topic of classification.topics ?? []) {\n if (normalizedText.includes(topic.toLowerCase())) {\n signals.push({\n type: 'topic',\n value: topic,\n weight: 0.3,\n });\n }\n }\n \n // 5. Context type (if we can infer work vs personal)\n // This is a weaker signal but helps with disambiguation\n const inferredContextType = inferContextType(normalizedText);\n if (inferredContextType === classification.context_type) {\n signals.push({\n type: 'context_type',\n value: classification.context_type,\n weight: 0.2,\n });\n }\n \n // Only include if we have at least one signal\n if (signals.length > 0) {\n const confidence = calculateConfidence(signals);\n results.push({\n projectId: route.projectId,\n confidence,\n signals,\n reasoning: buildReasoning(signals),\n });\n }\n }\n \n // Sort by confidence descending\n return results.sort((a, b) => b.confidence - a.confidence);\n };\n \n const calculateConfidence = (signals: ClassificationSignal[]): number => {\n if (signals.length === 0) return 0;\n \n // Weighted average with diminishing returns for multiple signals\n let totalWeight = 0;\n let weightedSum = 0;\n \n for (let i = 0; i < signals.length; i++) {\n const signal = signals[i];\n // Later signals contribute less (diminishing returns)\n const positionFactor = 1 / (1 + i * 0.3);\n const effectiveWeight = signal.weight * positionFactor;\n \n weightedSum += effectiveWeight;\n totalWeight += positionFactor;\n }\n \n // Normalize and cap at 0.99\n return Math.min(weightedSum / Math.max(totalWeight, 1), 0.99);\n };\n \n const buildReasoning = (signals: ClassificationSignal[]): string => {\n const parts = signals.map(s => {\n switch (s.type) {\n case 'explicit_phrase': return `explicit phrase: \"${s.value}\"`;\n case 'associated_person': return `mentioned ${s.value} (associated)`;\n case 'associated_company': return `mentioned ${s.value} (associated company)`;\n case 'topic': return `topic: ${s.value}`;\n case 'context_type': return `context: ${s.value}`;\n }\n });\n return parts.join(', ');\n };\n \n return { classify, calculateConfidence };\n};\n\n// Helper functions\nfunction detectPeopleFromContext(\n text: string, \n context: Context.ContextInstance\n): string[] {\n const found: string[] = [];\n \n for (const person of context.getAllPeople()) {\n const nameNormalized = person.name.toLowerCase();\n if (text.includes(nameNormalized)) {\n found.push(person.id);\n continue;\n }\n \n // Check phonetic variants (sounds_like)\n for (const variant of person.sounds_like ?? []) {\n if (text.includes(variant.toLowerCase())) {\n found.push(person.id);\n break;\n }\n }\n }\n \n return found;\n}\n\nfunction detectCompaniesFromContext(\n text: string, \n context: Context.ContextInstance\n): string[] {\n const found: string[] = [];\n \n for (const company of context.getAllCompanies()) {\n const nameNormalized = company.name.toLowerCase();\n if (text.includes(nameNormalized)) {\n found.push(company.id);\n continue;\n }\n \n // Check full name\n if (company.fullName && text.includes(company.fullName.toLowerCase())) {\n found.push(company.id);\n continue;\n }\n \n // Check phonetic variants (sounds_like)\n for (const variant of company.sounds_like ?? []) {\n if (text.includes(variant.toLowerCase())) {\n found.push(company.id);\n break;\n }\n }\n }\n \n return found;\n}\n\nfunction inferContextType(text: string): 'work' | 'personal' | 'mixed' {\n const workIndicators = ['meeting', 'project', 'deadline', 'team', 'client', 'report'];\n const personalIndicators = ['family', 'weekend', 'vacation', 'hobby', 'friend'];\n \n let workScore = 0;\n let personalScore = 0;\n \n for (const word of workIndicators) {\n if (text.includes(word)) workScore++;\n }\n \n for (const word of personalIndicators) {\n if (text.includes(word)) personalScore++;\n }\n \n if (workScore > personalScore + 1) return 'work';\n if (personalScore > workScore + 1) return 'personal';\n return 'mixed';\n}\n\n"],"names":[],"mappings":"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"}
|
package/dist/index29.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { getLogger } from './index41.js';
|
|
5
|
+
|
|
6
|
+
const print = console.log;
|
|
7
|
+
const createReadlineInterface = () => {
|
|
8
|
+
return readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
const askQuestion = (rl, question) => {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
rl.question(question, (answer) => {
|
|
16
|
+
resolve(answer.trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
const create = (analyzer, config) => {
|
|
21
|
+
const logger = getLogger();
|
|
22
|
+
const collectFeedback = async (decision) => {
|
|
23
|
+
const rl = createReadlineInterface();
|
|
24
|
+
try {
|
|
25
|
+
print("\n" + "─".repeat(60));
|
|
26
|
+
print("[Classification Feedback]");
|
|
27
|
+
print("─".repeat(60));
|
|
28
|
+
print(`
|
|
29
|
+
File: ${decision.audioFile}`);
|
|
30
|
+
print(`
|
|
31
|
+
Transcript preview:`);
|
|
32
|
+
print(` "${decision.transcriptPreview.substring(0, 200)}..."`);
|
|
33
|
+
print(`
|
|
34
|
+
Current Classification:`);
|
|
35
|
+
print(` Project: ${decision.projectId || "(default)"}`);
|
|
36
|
+
print(` Destination: ${decision.destination}`);
|
|
37
|
+
print(` Confidence: ${(decision.confidence * 100).toFixed(1)}%`);
|
|
38
|
+
print(` Reasoning: ${decision.reasoningTrace.finalReasoning}`);
|
|
39
|
+
print("");
|
|
40
|
+
const wasCorrect = await askQuestion(rl, "Was this classification correct? (y/n): ");
|
|
41
|
+
if (wasCorrect.toLowerCase() === "y" || wasCorrect.toLowerCase() === "yes") {
|
|
42
|
+
print("Great! No feedback needed.");
|
|
43
|
+
rl.close();
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
print("\nPlease provide the correct classification:");
|
|
47
|
+
const correctProject = await askQuestion(
|
|
48
|
+
rl,
|
|
49
|
+
'Correct project ID (or "new" to create, Enter to skip): '
|
|
50
|
+
);
|
|
51
|
+
let newProjectName;
|
|
52
|
+
if (correctProject.toLowerCase() === "new") {
|
|
53
|
+
newProjectName = await askQuestion(rl, "New project name: ");
|
|
54
|
+
}
|
|
55
|
+
const correctDestination = await askQuestion(
|
|
56
|
+
rl,
|
|
57
|
+
"Correct destination path (Enter to skip): "
|
|
58
|
+
);
|
|
59
|
+
const topicsInput = await askQuestion(
|
|
60
|
+
rl,
|
|
61
|
+
"Topics for this note (comma-separated, Enter to skip): "
|
|
62
|
+
);
|
|
63
|
+
const topics = topicsInput ? topicsInput.split(",").map((t) => t.trim()) : void 0;
|
|
64
|
+
const contextType = await askQuestion(
|
|
65
|
+
rl,
|
|
66
|
+
"Context type - work/personal/mixed (Enter to skip): "
|
|
67
|
+
);
|
|
68
|
+
print("\nPlease explain why the original classification was wrong:");
|
|
69
|
+
const userReason = await askQuestion(rl, "> ");
|
|
70
|
+
rl.close();
|
|
71
|
+
const feedback = {
|
|
72
|
+
transcriptPath: decision.audioFile,
|
|
73
|
+
originalDecision: {
|
|
74
|
+
projectId: decision.projectId,
|
|
75
|
+
destination: decision.destination,
|
|
76
|
+
confidence: decision.confidence,
|
|
77
|
+
reasoning: decision.reasoningTrace.finalReasoning
|
|
78
|
+
},
|
|
79
|
+
correction: {
|
|
80
|
+
projectId: correctProject === "new" ? newProjectName : correctProject || void 0,
|
|
81
|
+
destination: correctDestination || void 0,
|
|
82
|
+
topics,
|
|
83
|
+
contextType
|
|
84
|
+
},
|
|
85
|
+
userReason,
|
|
86
|
+
providedAt: /* @__PURE__ */ new Date()
|
|
87
|
+
};
|
|
88
|
+
return feedback;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
logger.error("Error collecting feedback", { error });
|
|
91
|
+
rl.close();
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const processFeedback = async (feedback) => {
|
|
96
|
+
print("\nAnalyzing feedback with reasoning model...");
|
|
97
|
+
return analyzer.analyze(feedback);
|
|
98
|
+
};
|
|
99
|
+
const reviewAndApply = async (analysis) => {
|
|
100
|
+
const rl = createReadlineInterface();
|
|
101
|
+
try {
|
|
102
|
+
print("\n" + "─".repeat(60));
|
|
103
|
+
print("[Feedback Analysis Results]");
|
|
104
|
+
print("─".repeat(60));
|
|
105
|
+
print(`
|
|
106
|
+
Diagnosis: ${analysis.diagnosis}`);
|
|
107
|
+
print(`Confidence: ${(analysis.confidence * 100).toFixed(1)}%`);
|
|
108
|
+
if (analysis.suggestedUpdates.length === 0) {
|
|
109
|
+
print("\nNo context updates suggested.");
|
|
110
|
+
rl.close();
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
print(`
|
|
114
|
+
Suggested Updates (${analysis.suggestedUpdates.length}):`);
|
|
115
|
+
const approvedUpdates = [];
|
|
116
|
+
for (let i = 0; i < analysis.suggestedUpdates.length; i++) {
|
|
117
|
+
const update = analysis.suggestedUpdates[i];
|
|
118
|
+
print(`
|
|
119
|
+
${i + 1}. [${update.type}] ${update.entityType}: ${update.entityId}`);
|
|
120
|
+
print(` Reasoning: ${update.reasoning}`);
|
|
121
|
+
print(` Confidence: ${(update.confidence * 100).toFixed(1)}%`);
|
|
122
|
+
print(" Changes:");
|
|
123
|
+
for (const change of update.changes) {
|
|
124
|
+
print(` - ${change.field}: ${JSON.stringify(change.newValue)}`);
|
|
125
|
+
}
|
|
126
|
+
const approve = await askQuestion(rl, ` Apply this update? (y/n/edit): `);
|
|
127
|
+
if (approve.toLowerCase() === "y" || approve.toLowerCase() === "yes") {
|
|
128
|
+
approvedUpdates.push(update);
|
|
129
|
+
} else if (approve.toLowerCase() === "edit") {
|
|
130
|
+
print(" (Editing not yet implemented, skipping)");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
rl.close();
|
|
134
|
+
if (approvedUpdates.length > 0) {
|
|
135
|
+
print(`
|
|
136
|
+
Applying ${approvedUpdates.length} updates...`);
|
|
137
|
+
await analyzer.applyUpdates(approvedUpdates);
|
|
138
|
+
print("Updates applied successfully.");
|
|
139
|
+
}
|
|
140
|
+
return approvedUpdates;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error("Error reviewing updates", { error });
|
|
143
|
+
rl.close();
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const saveFeedback = async (feedback, analysis) => {
|
|
148
|
+
await fs.mkdir(config.feedbackDir, { recursive: true });
|
|
149
|
+
const record = {
|
|
150
|
+
feedback,
|
|
151
|
+
analysis,
|
|
152
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
153
|
+
};
|
|
154
|
+
const filename = `feedback-${Date.now()}.json`;
|
|
155
|
+
const filepath = path.join(config.feedbackDir, filename);
|
|
156
|
+
await fs.writeFile(filepath, JSON.stringify(record, null, 2), "utf-8");
|
|
157
|
+
logger.info("Saved feedback to: %s", filepath);
|
|
158
|
+
};
|
|
159
|
+
return { collectFeedback, processFeedback, reviewAndApply, saveFeedback };
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export { create };
|
|
163
|
+
//# sourceMappingURL=index29.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index29.js","sources":["../src/feedback/handler.ts"],"sourcesContent":["/**\n * Feedback Handler\n * \n * Handles the interactive feedback process - collecting user input,\n * analyzing it, and applying learned updates.\n */\n\nimport * as readline from 'readline';\nimport * as fs from 'fs/promises';\nimport * as path from 'node:path';\nimport { ClassificationFeedback, ClassificationDecision, FeedbackAnalysis, LearningUpdate } from './types';\nimport * as Analyzer from './analyzer';\nimport * as Logging from '../logging';\n\n// CLI output helper - allowed for interactive CLI\n// eslint-disable-next-line no-console\nconst print = console.log;\n\nexport interface HandlerInstance {\n collectFeedback(decision: ClassificationDecision): Promise<ClassificationFeedback | null>;\n processFeedback(feedback: ClassificationFeedback): Promise<FeedbackAnalysis>;\n reviewAndApply(analysis: FeedbackAnalysis): Promise<LearningUpdate[]>;\n saveFeedback(feedback: ClassificationFeedback, analysis: FeedbackAnalysis): Promise<void>;\n}\n\nexport interface HandlerConfig {\n feedbackDir: string;\n interactive: boolean;\n}\n\nconst createReadlineInterface = () => {\n return readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n};\n\nconst askQuestion = (rl: readline.Interface, question: string): Promise<string> => {\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n resolve(answer.trim());\n });\n });\n};\n\nexport const create = (\n analyzer: Analyzer.AnalyzerInstance,\n config: HandlerConfig\n): HandlerInstance => {\n const logger = Logging.getLogger();\n\n const collectFeedback = async (decision: ClassificationDecision): Promise<ClassificationFeedback | null> => {\n if (!config.interactive) {\n logger.warn('Feedback collection requires interactive mode');\n return null;\n }\n\n const rl = createReadlineInterface();\n\n try {\n print('\\n' + '─'.repeat(60));\n print('[Classification Feedback]');\n print('─'.repeat(60));\n print(`\\nFile: ${decision.audioFile}`);\n print(`\\nTranscript preview:`);\n print(` \"${decision.transcriptPreview.substring(0, 200)}...\"`);\n print(`\\nCurrent Classification:`);\n print(` Project: ${decision.projectId || '(default)'}`);\n print(` Destination: ${decision.destination}`);\n print(` Confidence: ${(decision.confidence * 100).toFixed(1)}%`);\n print(` Reasoning: ${decision.reasoningTrace.finalReasoning}`);\n print('');\n\n // Ask if classification was correct\n const wasCorrect = await askQuestion(rl, 'Was this classification correct? (y/n): ');\n \n if (wasCorrect.toLowerCase() === 'y' || wasCorrect.toLowerCase() === 'yes') {\n print('Great! No feedback needed.');\n rl.close();\n return null;\n }\n\n // Collect correction\n print('\\nPlease provide the correct classification:');\n \n const correctProject = await askQuestion(rl, \n 'Correct project ID (or \"new\" to create, Enter to skip): '\n );\n \n let newProjectName: string | undefined;\n if (correctProject.toLowerCase() === 'new') {\n newProjectName = await askQuestion(rl, 'New project name: ');\n }\n\n const correctDestination = await askQuestion(rl,\n 'Correct destination path (Enter to skip): '\n );\n\n const topicsInput = await askQuestion(rl,\n 'Topics for this note (comma-separated, Enter to skip): '\n );\n const topics = topicsInput ? topicsInput.split(',').map(t => t.trim()) : undefined;\n\n const contextType = await askQuestion(rl,\n 'Context type - work/personal/mixed (Enter to skip): '\n );\n\n print('\\nPlease explain why the original classification was wrong:');\n const userReason = await askQuestion(rl, '> ');\n\n rl.close();\n\n const feedback: ClassificationFeedback = {\n transcriptPath: decision.audioFile,\n originalDecision: {\n projectId: decision.projectId,\n destination: decision.destination,\n confidence: decision.confidence,\n reasoning: decision.reasoningTrace.finalReasoning,\n },\n correction: {\n projectId: correctProject === 'new' ? newProjectName : (correctProject || undefined),\n destination: correctDestination || undefined,\n topics,\n contextType: contextType as 'work' | 'personal' | 'mixed' | undefined,\n },\n userReason,\n providedAt: new Date(),\n };\n\n return feedback;\n\n } catch (error) {\n logger.error('Error collecting feedback', { error });\n rl.close();\n return null;\n }\n };\n\n const processFeedback = async (feedback: ClassificationFeedback): Promise<FeedbackAnalysis> => {\n print('\\nAnalyzing feedback with reasoning model...');\n return analyzer.analyze(feedback);\n };\n\n const reviewAndApply = async (analysis: FeedbackAnalysis): Promise<LearningUpdate[]> => {\n if (!config.interactive) {\n // In non-interactive mode, auto-apply high-confidence updates\n const highConfidence = analysis.suggestedUpdates.filter(u => u.confidence >= 0.8);\n if (highConfidence.length > 0) {\n await analyzer.applyUpdates(highConfidence);\n }\n return highConfidence;\n }\n\n const rl = createReadlineInterface();\n\n try {\n print('\\n' + '─'.repeat(60));\n print('[Feedback Analysis Results]');\n print('─'.repeat(60));\n print(`\\nDiagnosis: ${analysis.diagnosis}`);\n print(`Confidence: ${(analysis.confidence * 100).toFixed(1)}%`);\n\n if (analysis.suggestedUpdates.length === 0) {\n print('\\nNo context updates suggested.');\n rl.close();\n return [];\n }\n\n print(`\\nSuggested Updates (${analysis.suggestedUpdates.length}):`);\n \n const approvedUpdates: LearningUpdate[] = [];\n\n for (let i = 0; i < analysis.suggestedUpdates.length; i++) {\n const update = analysis.suggestedUpdates[i];\n print(`\\n${i + 1}. [${update.type}] ${update.entityType}: ${update.entityId}`);\n print(` Reasoning: ${update.reasoning}`);\n print(` Confidence: ${(update.confidence * 100).toFixed(1)}%`);\n print(' Changes:');\n for (const change of update.changes) {\n print(` - ${change.field}: ${JSON.stringify(change.newValue)}`);\n }\n\n const approve = await askQuestion(rl, ` Apply this update? (y/n/edit): `);\n \n if (approve.toLowerCase() === 'y' || approve.toLowerCase() === 'yes') {\n approvedUpdates.push(update);\n } else if (approve.toLowerCase() === 'edit') {\n // Allow editing the update\n print(' (Editing not yet implemented, skipping)');\n }\n }\n\n rl.close();\n\n if (approvedUpdates.length > 0) {\n print(`\\nApplying ${approvedUpdates.length} updates...`);\n await analyzer.applyUpdates(approvedUpdates);\n print('Updates applied successfully.');\n }\n\n return approvedUpdates;\n\n } catch (error) {\n logger.error('Error reviewing updates', { error });\n rl.close();\n return [];\n }\n };\n\n const saveFeedback = async (feedback: ClassificationFeedback, analysis: FeedbackAnalysis): Promise<void> => {\n // Ensure feedback directory exists\n await fs.mkdir(config.feedbackDir, { recursive: true });\n\n // Create feedback record\n const record = {\n feedback,\n analysis,\n savedAt: new Date().toISOString(),\n };\n\n // Save with timestamp\n const filename = `feedback-${Date.now()}.json`;\n const filepath = path.join(config.feedbackDir, filename);\n \n await fs.writeFile(filepath, JSON.stringify(record, null, 2), 'utf-8');\n logger.info('Saved feedback to: %s', filepath);\n };\n\n return { collectFeedback, processFeedback, reviewAndApply, saveFeedback };\n};\n\n"],"names":["Logging.getLogger"],"mappings":"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"}
|
package/dist/index3.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { create as create$1 } from './index20.js';
|
|
3
|
+
export { MODEL_CAPABILITIES } from './index21.js';
|
|
4
|
+
|
|
5
|
+
const create = (options = {}) => {
|
|
6
|
+
let service = null;
|
|
7
|
+
const getService = () => {
|
|
8
|
+
if (!service) {
|
|
9
|
+
const openai = options.openaiClient ?? new OpenAI({ apiKey: options.apiKey });
|
|
10
|
+
service = create$1(openai);
|
|
11
|
+
}
|
|
12
|
+
return service;
|
|
13
|
+
};
|
|
14
|
+
let defaultModel = options.defaultModel ?? "whisper-1";
|
|
15
|
+
const transcribe = async (audioFile, configOptions = {}) => {
|
|
16
|
+
return getService().transcribe({
|
|
17
|
+
audioFile,
|
|
18
|
+
config: {
|
|
19
|
+
model: configOptions.model ?? defaultModel,
|
|
20
|
+
...configOptions
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
transcribe,
|
|
26
|
+
supportsStreaming: (model) => getService().supportsStreaming(model),
|
|
27
|
+
supportsDiarization: (model) => getService().supportsDiarization(model),
|
|
28
|
+
setDefaultModel: (model) => {
|
|
29
|
+
defaultModel = model;
|
|
30
|
+
},
|
|
31
|
+
getDefaultModel: () => defaultModel
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { create };
|
|
36
|
+
//# sourceMappingURL=index3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index3.js","sources":["../src/transcription/index.ts"],"sourcesContent":["/**\n * Transcription System\n * \n * Main entry point for the transcription system. Provides a factory function\n * to create transcription instances that can transcribe audio files using\n * various OpenAI models.\n * \n * Design Philosophy:\n * - Keep transcription simple - it produces raw phonetic output\n * - The reasoning pass handles corrections with full context\n * - Model choice is user preference (quality vs cost)\n */\n\nimport OpenAI from 'openai';\nimport { TranscriptionConfig, TranscriptionResult, TranscriptionModel } from './types';\nimport * as Service from './service';\n\nexport interface TranscriptionInstance {\n // Core transcription\n transcribe(audioFile: string, options?: Partial<TranscriptionConfig>): Promise<TranscriptionResult>;\n \n // Model capabilities\n supportsStreaming(model: TranscriptionModel): boolean;\n supportsDiarization(model: TranscriptionModel): boolean;\n \n // Configuration\n setDefaultModel(model: TranscriptionModel): void;\n getDefaultModel(): TranscriptionModel;\n}\n\nexport interface CreateOptions {\n apiKey?: string;\n defaultModel?: TranscriptionModel;\n openaiClient?: OpenAI;\n}\n\nexport const create = (options: CreateOptions = {}): TranscriptionInstance => {\n // Lazy-initialize OpenAI client (only when actually needed for transcription)\n let service: Service.ServiceInstance | null = null;\n const getService = (): Service.ServiceInstance => {\n if (!service) {\n const openai = options.openaiClient ?? new OpenAI({ apiKey: options.apiKey });\n service = Service.create(openai);\n }\n return service;\n };\n \n let defaultModel: TranscriptionModel = options.defaultModel ?? 'whisper-1';\n \n const transcribe = async (\n audioFile: string, \n configOptions: Partial<TranscriptionConfig> = {}\n ): Promise<TranscriptionResult> => {\n return getService().transcribe({\n audioFile,\n config: {\n model: configOptions.model ?? defaultModel,\n ...configOptions,\n },\n });\n };\n \n return {\n transcribe,\n supportsStreaming: (model) => getService().supportsStreaming(model),\n supportsDiarization: (model) => getService().supportsDiarization(model),\n setDefaultModel: (model) => { defaultModel = model; },\n getDefaultModel: () => defaultModel,\n };\n};\n\n// Re-export types\nexport * from './types';\n\n"],"names":["Service.create"],"mappings":";;;;AAoCO,MAAM,MAAA,GAAS,CAAC,OAAA,GAAyB,EAAC,KAA6B;AAE1E,EAAA,IAAI,OAAA,GAA0C,IAAA;AAC9C,EAAA,MAAM,aAAa,MAA+B;AAC9C,IAAA,IAAI,CAAC,OAAA,EAAS;AACV,MAAA,MAAM,MAAA,GAAS,QAAQ,YAAA,IAAgB,IAAI,OAAO,EAAE,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAA;AAC5E,MAAA,OAAA,GAAUA,SAAe,MAAM,CAAA;AAAA,IACnC;AACA,IAAA,OAAO,OAAA;AAAA,EACX,CAAA;AAEA,EAAA,IAAI,YAAA,GAAmC,QAAQ,YAAA,IAAgB,WAAA;AAE/D,EAAA,MAAM,UAAA,GAAa,OACf,SAAA,EACA,aAAA,GAA8C,EAAC,KAChB;AAC/B,IAAA,OAAO,UAAA,GAAa,UAAA,CAAW;AAAA,MAC3B,SAAA;AAAA,MACA,MAAA,EAAQ;AAAA,QACJ,KAAA,EAAO,cAAc,KAAA,IAAS,YAAA;AAAA,QAC9B,GAAG;AAAA;AACP,KACH,CAAA;AAAA,EACL,CAAA;AAEA,EAAA,OAAO;AAAA,IACH,UAAA;AAAA,IACA,mBAAmB,CAAC,KAAA,KAAU,UAAA,EAAW,CAAE,kBAAkB,KAAK,CAAA;AAAA,IAClE,qBAAqB,CAAC,KAAA,KAAU,UAAA,EAAW,CAAE,oBAAoB,KAAK,CAAA;AAAA,IACtE,eAAA,EAAiB,CAAC,KAAA,KAAU;AAAE,MAAA,YAAA,GAAe,KAAA;AAAA,IAAO,CAAA;AAAA,IACpD,iBAAiB,MAAM;AAAA,GAC3B;AACJ;;;;"}
|