@redaksjon/protokoll 0.0.6
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/.nvmrc +2 -0
- package/LICENSE +190 -0
- package/README.md +88 -0
- package/dist/arguments.js +158 -0
- package/dist/arguments.js.map +1 -0
- package/dist/constants.js +82 -0
- package/dist/constants.js.map +1 -0
- package/dist/logging.js +46 -0
- package/dist/logging.js.map +1 -0
- package/dist/main.js +5 -0
- package/dist/main.js.map +1 -0
- package/dist/phases/locate.js +55 -0
- package/dist/phases/locate.js.map +1 -0
- package/dist/phases/transcribe.js +149 -0
- package/dist/phases/transcribe.js.map +1 -0
- package/dist/processor.js +35 -0
- package/dist/processor.js.map +1 -0
- package/dist/prompt/instructions/transcribe.md +46 -0
- package/dist/prompt/personas/transcriber.md +37 -0
- package/dist/prompt/transcribe.js +41 -0
- package/dist/prompt/transcribe.js.map +1 -0
- package/dist/protokoll.js +81 -0
- package/dist/protokoll.js.map +1 -0
- package/dist/util/dates.js +96 -0
- package/dist/util/dates.js.map +1 -0
- package/dist/util/general.js +39 -0
- package/dist/util/general.js.map +1 -0
- package/dist/util/media.js +103 -0
- package/dist/util/media.js.map +1 -0
- package/dist/util/openai.js +92 -0
- package/dist/util/openai.js.map +1 -0
- package/dist/util/storage.js +135 -0
- package/dist/util/storage.js.map +1 -0
- package/docs/index.html +16 -0
- package/docs/package-lock.json +1521 -0
- package/docs/package.json +21 -0
- package/docs/vite.config.js +10 -0
- package/eslint.config.mjs +82 -0
- package/nodemon.json +14 -0
- package/output/kodrdriv/250702-1905-commit-message.md +1 -0
- package/output/kodrdriv/250702-1905-commit.request.json +14 -0
- package/output/kodrdriv/250702-1905-commit.response.json +36 -0
- package/output/kodrdriv/250702-1906-commit-message.md +1 -0
- package/output/kodrdriv/250702-1907-commit-message.md +1 -0
- package/output/kodrdriv/250702-1907-commit.request.json +14 -0
- package/output/kodrdriv/250702-1907-commit.response.json +36 -0
- package/output/kodrdriv/250716-1517-review-analysis.md +39 -0
- package/output/kodrdriv/250716-1517-review-notes.md +69 -0
- package/output/kodrdriv/250716-1518-review-analysis.md +15 -0
- package/output/kodrdriv/250716-1518-review-notes.md +67 -0
- package/output/kodrdriv/250716-1523-review-analysis.md +36 -0
- package/output/kodrdriv/250716-1523-review-notes.md +87 -0
- package/output/kodrdriv/250722-1135-commit-message.md +1 -0
- package/output/kodrdriv/250722-1331-commit-message.md +1 -0
- package/output/kodrdriv/250722-1335-commit-message.md +1 -0
- package/output/kodrdriv/250722-1337-commit-message.md +1 -0
- package/output/kodrdriv/250722-1342-release-notes.md +26 -0
- package/output/kodrdriv/250722-1416-commit-message.md +3 -0
- package/output/kodrdriv/250722-1420-commit-message.md +1 -0
- package/output/kodrdriv/250722-1422-commit-message.md +1 -0
- package/output/kodrdriv/250722-1423-commit-message.md +1 -0
- package/output/kodrdriv/250722-1425-release-notes.md +41 -0
- package/output/kodrdriv/250722-1527-commit-message.md +13 -0
- package/output/kodrdriv/250722-1532-commit-message.md +1 -0
- package/output/kodrdriv/250722-1532-release-notes.md +32 -0
- package/output/kodrdriv/250722-2314-review-analysis.md +28 -0
- package/output/kodrdriv/250722-2314-review-notes.md +464 -0
- package/output/kodrdriv/250722-2315-review-analysis.md +28 -0
- package/output/kodrdriv/250722-2315-review-notes.md +477 -0
- package/output/kodrdriv/250804-1623-review-analysis.md +38 -0
- package/output/kodrdriv/250804-1623-review-notes.md +479 -0
- package/output/kodrdriv/250804-1638-review-analysis.md +56 -0
- package/output/kodrdriv/250804-1638-review-notes.md +502 -0
- package/output/kodrdriv/250812-2021-review-analysis.md +27 -0
- package/output/kodrdriv/250812-2021-review-notes.md +571 -0
- package/output/kodrdriv/250826-0700-commit-message.md +12 -0
- package/output/kodrdriv/RELEASE_NOTES.md +30 -0
- package/output/kodrdriv/RELEASE_TITLE.md +1 -0
- package/package.json +78 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +124 -0
- package/vitest.config.ts +30 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getLogger } from '../logging.js';
|
|
2
|
+
import { create as create$3 } from '../util/media.js';
|
|
3
|
+
import { create as create$1 } from '../util/storage.js';
|
|
4
|
+
import { create as create$2 } from '../util/dates.js';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
const create = (config, operator)=>{
|
|
8
|
+
const logger = getLogger();
|
|
9
|
+
const storage = create$1({
|
|
10
|
+
log: logger.debug
|
|
11
|
+
});
|
|
12
|
+
const dates = create$2({
|
|
13
|
+
timezone: config.timezone
|
|
14
|
+
});
|
|
15
|
+
const media = create$3(logger);
|
|
16
|
+
const locate = async (audioFile)=>{
|
|
17
|
+
logger.debug('Processing file %s', audioFile);
|
|
18
|
+
// Extract audio file creation time
|
|
19
|
+
let creationTime = await media.getAudioCreationTime(audioFile);
|
|
20
|
+
try {
|
|
21
|
+
if (creationTime) {
|
|
22
|
+
logger.info('Audio recording time: %s', creationTime.toISOString());
|
|
23
|
+
} else {
|
|
24
|
+
logger.warn('Could not determine audio recording time for %s, using current date', audioFile);
|
|
25
|
+
creationTime = dates.now();
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
logger.error('Error determining audio recording time for %s: %s, using current date', audioFile, error.message);
|
|
29
|
+
creationTime = dates.now();
|
|
30
|
+
}
|
|
31
|
+
// Calculate the hash of file and output directory
|
|
32
|
+
const hash = (await storage.hashFile(audioFile, 100)).substring(0, 8);
|
|
33
|
+
const outputPath = await operator.constructOutputDirectory(creationTime);
|
|
34
|
+
const contextPath = path.join(outputPath, '.context');
|
|
35
|
+
await storage.createDirectory(contextPath);
|
|
36
|
+
const interimPath = path.join(outputPath, '.interim');
|
|
37
|
+
await storage.createDirectory(interimPath);
|
|
38
|
+
const transcriptionFilename = await operator.constructFilename(creationTime, 'transcription', hash);
|
|
39
|
+
return {
|
|
40
|
+
creationTime,
|
|
41
|
+
outputPath,
|
|
42
|
+
contextPath,
|
|
43
|
+
interimPath,
|
|
44
|
+
transcriptionFilename,
|
|
45
|
+
hash,
|
|
46
|
+
audioFile
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
locate
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { create };
|
|
55
|
+
//# sourceMappingURL=locate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locate.js","sources":["../../src/phases/locate.ts"],"sourcesContent":["import * as Logging from '@/logging';\nimport * as Media from '@/util/media';\nimport * as Storage from '@/util/storage';\nimport * as Dreadcabinet from '@theunwalked/dreadcabinet';\nimport * as Dates from '@/util/dates';\nimport { Config } from '@/protokoll';\nimport path from 'path';\n\n// Helper function to promisify ffmpeg.\n\nexport interface Instance {\n locate: (audioFile: string) => Promise<{\n creationTime: Date;\n outputPath: string;\n contextPath: string;\n interimPath: string;\n transcriptionFilename: string;\n hash: string;\n audioFile: string;\n }>;\n}\n\nexport const create = (config: Config, operator: Dreadcabinet.Operator): Instance => {\n const logger = Logging.getLogger();\n const storage = Storage.create({ log: logger.debug });\n const dates = Dates.create({ timezone: config.timezone });\n const media = Media.create(logger);\n\n const locate = async (audioFile: string): Promise<{\n creationTime: Date;\n outputPath: string;\n contextPath: string;\n interimPath: string;\n transcriptionFilename: string;\n hash: string;\n audioFile: string;\n }> => {\n logger.debug('Processing file %s', audioFile);\n\n // Extract audio file creation time\n let creationTime = await media.getAudioCreationTime(audioFile);\n try {\n if (creationTime) {\n logger.info('Audio recording time: %s', creationTime.toISOString());\n } else {\n logger.warn('Could not determine audio recording time for %s, using current date', audioFile);\n creationTime = dates.now();\n }\n } catch (error: any) {\n logger.error('Error determining audio recording time for %s: %s, using current date', audioFile, error.message);\n creationTime = dates.now();\n }\n\n // Calculate the hash of file and output directory\n const hash = (await storage.hashFile(audioFile, 100)).substring(0, 8);\n const outputPath: string = await operator.constructOutputDirectory(creationTime);\n const contextPath: string = path.join(outputPath, '.context');\n await storage.createDirectory(contextPath);\n const interimPath: string = path.join(outputPath, '.interim');\n await storage.createDirectory(interimPath);\n const transcriptionFilename = await operator.constructFilename(creationTime, 'transcription', hash);\n\n return {\n creationTime,\n outputPath,\n contextPath,\n interimPath,\n transcriptionFilename,\n hash,\n audioFile,\n };\n }\n\n return {\n locate,\n }\n}\n\n\n"],"names":["create","config","operator","logger","Logging","storage","Storage","log","debug","dates","Dates","timezone","media","Media","locate","audioFile","creationTime","getAudioCreationTime","info","toISOString","warn","now","error","message","hash","hashFile","substring","outputPath","constructOutputDirectory","contextPath","path","join","createDirectory","interimPath","transcriptionFilename","constructFilename"],"mappings":";;;;;;AAsBO,MAAMA,MAAAA,GAAS,CAACC,MAAAA,EAAgBC,QAAAA,GAAAA;IACnC,MAAMC,MAAAA,GAASC,SAAiB,EAAA;IAChC,MAAMC,OAAAA,GAAUC,QAAc,CAAC;AAAEC,QAAAA,GAAAA,EAAKJ,OAAOK;AAAM,KAAA,CAAA;IACnD,MAAMC,KAAAA,GAAQC,QAAY,CAAC;AAAEC,QAAAA,QAAAA,EAAUV,OAAOU;AAAS,KAAA,CAAA;IACvD,MAAMC,KAAAA,GAAQC,QAAY,CAACV,MAAAA,CAAAA;AAE3B,IAAA,MAAMW,SAAS,OAAOC,SAAAA,GAAAA;QASlBZ,MAAAA,CAAOK,KAAK,CAAC,oBAAA,EAAsBO,SAAAA,CAAAA;;AAGnC,QAAA,IAAIC,YAAAA,GAAe,MAAMJ,KAAAA,CAAMK,oBAAoB,CAACF,SAAAA,CAAAA;QACpD,IAAI;AACA,YAAA,IAAIC,YAAAA,EAAc;AACdb,gBAAAA,MAAAA,CAAOe,IAAI,CAAC,0BAAA,EAA4BF,YAAAA,CAAaG,WAAW,EAAA,CAAA;YACpE,CAAA,MAAO;gBACHhB,MAAAA,CAAOiB,IAAI,CAAC,qEAAA,EAAuEL,SAAAA,CAAAA;AACnFC,gBAAAA,YAAAA,GAAeP,MAAMY,GAAG,EAAA;AAC5B,YAAA;AACJ,QAAA,CAAA,CAAE,OAAOC,KAAAA,EAAY;AACjBnB,YAAAA,MAAAA,CAAOmB,KAAK,CAAC,uEAAA,EAAyEP,SAAAA,EAAWO,MAAMC,OAAO,CAAA;AAC9GP,YAAAA,YAAAA,GAAeP,MAAMY,GAAG,EAAA;AAC5B,QAAA;;AAGA,QAAA,MAAMG,IAAAA,GAAQ,CAAA,MAAMnB,OAAAA,CAAQoB,QAAQ,CAACV,SAAAA,EAAW,GAAA,CAAG,EAAGW,SAAS,CAAC,CAAA,EAAG,CAAA,CAAA;AACnE,QAAA,MAAMC,UAAAA,GAAqB,MAAMzB,QAAAA,CAAS0B,wBAAwB,CAACZ,YAAAA,CAAAA;AACnE,QAAA,MAAMa,WAAAA,GAAsBC,IAAAA,CAAKC,IAAI,CAACJ,UAAAA,EAAY,UAAA,CAAA;QAClD,MAAMtB,OAAAA,CAAQ2B,eAAe,CAACH,WAAAA,CAAAA;AAC9B,QAAA,MAAMI,WAAAA,GAAsBH,IAAAA,CAAKC,IAAI,CAACJ,UAAAA,EAAY,UAAA,CAAA;QAClD,MAAMtB,OAAAA,CAAQ2B,eAAe,CAACC,WAAAA,CAAAA;AAC9B,QAAA,MAAMC,wBAAwB,MAAMhC,QAAAA,CAASiC,iBAAiB,CAACnB,cAAc,eAAA,EAAiBQ,IAAAA,CAAAA;QAE9F,OAAO;AACHR,YAAAA,YAAAA;AACAW,YAAAA,UAAAA;AACAE,YAAAA,WAAAA;AACAI,YAAAA,WAAAA;AACAC,YAAAA,qBAAAA;AACAV,YAAAA,IAAAA;AACAT,YAAAA;AACJ,SAAA;AACJ,IAAA,CAAA;IAEA,OAAO;AACHD,QAAAA;AACJ,KAAA;AACJ;;;;"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { getLogger } from '../logging.js';
|
|
2
|
+
import { create as create$1 } from '../util/storage.js';
|
|
3
|
+
import { create as create$2 } from '../util/media.js';
|
|
4
|
+
import { transcribeAudio, createCompletion } from '../util/openai.js';
|
|
5
|
+
import { stringifyJSON } from '../util/general.js';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { Formatter } from '@riotprompt/riotprompt';
|
|
8
|
+
import { create as create$3 } from '../prompt/transcribe.js';
|
|
9
|
+
|
|
10
|
+
const create = (config, operator)=>{
|
|
11
|
+
const logger = getLogger();
|
|
12
|
+
const storage = create$1({
|
|
13
|
+
log: logger.debug
|
|
14
|
+
});
|
|
15
|
+
const media = create$2(logger);
|
|
16
|
+
const prompts = create$3(config.model, config);
|
|
17
|
+
const transcribe = async (creation, outputPath, contextPath, interimPath, filename, hash, audioFile)=>{
|
|
18
|
+
if (!outputPath) {
|
|
19
|
+
throw new Error("outputPath is required for transcribe function");
|
|
20
|
+
}
|
|
21
|
+
if (!audioFile) {
|
|
22
|
+
throw new Error("audioFile is required for transcribe function");
|
|
23
|
+
}
|
|
24
|
+
// Remove extension from audioFile and make the name filesafe
|
|
25
|
+
const audioFileBasename = path.basename(audioFile, path.extname(audioFile)).replace(/[^a-zA-Z0-9_-]/g, '_') // Replace non-alphanumeric chars with underscore
|
|
26
|
+
.replace(/_+/g, '_') // Replace multiple underscores with a single one
|
|
27
|
+
.trim();
|
|
28
|
+
logger.debug(`Processed audio filename: ${audioFileBasename}`);
|
|
29
|
+
let transcriptOutputFilename = await operator.constructFilename(creation, 'transcript', hash, {
|
|
30
|
+
subject: audioFileBasename
|
|
31
|
+
});
|
|
32
|
+
// Ensure the filename ends with .json
|
|
33
|
+
if (!transcriptOutputFilename.endsWith('.json')) {
|
|
34
|
+
logger.warn('constructFilename did not return a .json file for transcript, appending extension: %s', transcriptOutputFilename);
|
|
35
|
+
transcriptOutputFilename += '.json';
|
|
36
|
+
}
|
|
37
|
+
const transcriptOutputPath = path.join(interimPath, transcriptOutputFilename);
|
|
38
|
+
// Check if transcription already exists
|
|
39
|
+
if (await storage.exists(transcriptOutputPath)) {
|
|
40
|
+
logger.info('Transcription file %s already exists, returning existing content...', transcriptOutputPath);
|
|
41
|
+
const existingContent = await storage.readFile(transcriptOutputPath, 'utf8');
|
|
42
|
+
return JSON.parse(existingContent);
|
|
43
|
+
}
|
|
44
|
+
const baseDebugFilename = path.parse(transcriptOutputFilename).name;
|
|
45
|
+
const transcriptionDebugFile = config.debug ? path.join(interimPath, `${baseDebugFilename}.transcription.raw.response.json`) : undefined;
|
|
46
|
+
// Check if audio file exceeds the size limit
|
|
47
|
+
const fileSize = await media.getFileSize(audioFile);
|
|
48
|
+
logger.debug(`Audio file size: ${fileSize} bytes, max size: ${config.maxAudioSize} bytes`);
|
|
49
|
+
let transcription;
|
|
50
|
+
if (fileSize > config.maxAudioSize) {
|
|
51
|
+
logger.info(`Audio file exceeds maximum size (${fileSize} > ${config.maxAudioSize} bytes), splitting into chunks`);
|
|
52
|
+
// Create a temporary directory for the audio chunks
|
|
53
|
+
const tempDir = path.join(config.tempDirectory, `split_audio_${hash}`);
|
|
54
|
+
await storage.createDirectory(tempDir);
|
|
55
|
+
try {
|
|
56
|
+
// Split the audio file into chunks
|
|
57
|
+
const audioChunks = await media.splitAudioFile(audioFile, tempDir, config.maxAudioSize);
|
|
58
|
+
logger.info(`Split audio file into ${audioChunks.length} chunks`);
|
|
59
|
+
// Transcribe each chunk
|
|
60
|
+
const transcriptions = [];
|
|
61
|
+
for(let i = 0; i < audioChunks.length; i++){
|
|
62
|
+
const chunkPath = audioChunks[i];
|
|
63
|
+
logger.info(`Transcribing chunk ${i + 1}/${audioChunks.length}: ${chunkPath}`);
|
|
64
|
+
const chunkDebugFile = config.debug ? path.join(interimPath, `${baseDebugFilename}.transcription.chunk${i + 1}.raw.response.json`) : undefined;
|
|
65
|
+
const chunkTranscription = await transcribeAudio(chunkPath, {
|
|
66
|
+
model: config.transcriptionModel,
|
|
67
|
+
debug: config.debug,
|
|
68
|
+
debugFile: chunkDebugFile
|
|
69
|
+
});
|
|
70
|
+
transcriptions.push(chunkTranscription);
|
|
71
|
+
}
|
|
72
|
+
// Combine all transcriptions
|
|
73
|
+
const combinedText = transcriptions.map((t)=>t.text).join(' ');
|
|
74
|
+
transcription = {
|
|
75
|
+
text: combinedText
|
|
76
|
+
};
|
|
77
|
+
// Save each individual chunk for debugging
|
|
78
|
+
await storage.writeFile(path.join(interimPath, `${baseDebugFilename}.transcription.combined.json`), stringifyJSON({
|
|
79
|
+
chunks: transcriptions,
|
|
80
|
+
combined: transcription
|
|
81
|
+
}), 'utf8');
|
|
82
|
+
// Clean up temporary files if not in debug mode
|
|
83
|
+
if (!config.debug) {
|
|
84
|
+
for (const chunk of audioChunks){
|
|
85
|
+
try {
|
|
86
|
+
await storage.deleteFile(chunk);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.warn(`Failed to delete temporary chunk ${chunk}: ${error}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logger.error(`Error processing split audio files: ${error}`);
|
|
94
|
+
throw new Error(`Failed to process split audio files: ${error}`);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
// If file size is within the limit, transcribe normally
|
|
98
|
+
transcription = await transcribeAudio(audioFile, {
|
|
99
|
+
model: config.transcriptionModel,
|
|
100
|
+
debug: config.debug,
|
|
101
|
+
debugFile: transcriptionDebugFile
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// Save the transcription
|
|
105
|
+
await storage.writeFile(transcriptOutputPath, stringifyJSON(transcription), 'utf8');
|
|
106
|
+
logger.debug('Wrote transcription to %s', transcriptOutputPath);
|
|
107
|
+
// Create markdown version of the transcript
|
|
108
|
+
const markdownOutputFilename = transcriptOutputFilename.replace('.json', '.md');
|
|
109
|
+
const markdownOutputPath = path.join(outputPath, markdownOutputFilename);
|
|
110
|
+
// Only create the markdown file if it doesn't already exist
|
|
111
|
+
if (!await storage.exists(markdownOutputPath)) {
|
|
112
|
+
logger.info('Creating Markdown version of the transcription...');
|
|
113
|
+
// Create a prompt for the transcription formatting task
|
|
114
|
+
const prompt = await prompts.createTranscribePrompt(transcription.text);
|
|
115
|
+
// Format the prompt using the override utility
|
|
116
|
+
const formatter = Formatter.create();
|
|
117
|
+
const chatRequest = formatter.formatPrompt(config.model, prompt);
|
|
118
|
+
// Debug file paths for the request and response
|
|
119
|
+
const requestDebugFile = config.debug ? path.join(interimPath, `${baseDebugFilename}.markdown.request.json`) : undefined;
|
|
120
|
+
const responseDebugFile = config.debug ? path.join(interimPath, `${baseDebugFilename}.markdown.response.json`) : undefined;
|
|
121
|
+
// Write debug file for the request if in debug mode
|
|
122
|
+
if (config.debug && requestDebugFile) {
|
|
123
|
+
await storage.writeFile(requestDebugFile, stringifyJSON(chatRequest), 'utf8');
|
|
124
|
+
logger.debug('Wrote chat request to %s', requestDebugFile);
|
|
125
|
+
}
|
|
126
|
+
// Call the model to convert the transcription to markdown
|
|
127
|
+
const markdownContent = await createCompletion(chatRequest.messages, {
|
|
128
|
+
model: config.model,
|
|
129
|
+
debug: config.debug,
|
|
130
|
+
debugFile: responseDebugFile
|
|
131
|
+
});
|
|
132
|
+
// Save the markdown version
|
|
133
|
+
await storage.writeFile(markdownOutputPath, markdownContent, 'utf8');
|
|
134
|
+
logger.debug('Wrote markdown transcription to %s', markdownOutputPath);
|
|
135
|
+
} else {
|
|
136
|
+
logger.info('Markdown transcription file %s already exists, skipping...', markdownOutputPath);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
...transcription,
|
|
140
|
+
audioFileBasename
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
transcribe
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export { create };
|
|
149
|
+
//# sourceMappingURL=transcribe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcribe.js","sources":["../../src/phases/transcribe.ts"],"sourcesContent":["import * as Dreadcabinet from '@theunwalked/dreadcabinet';\nimport { Config } from '@/protokoll';\nimport * as Logging from '@/logging';\nimport * as Storage from '@/util/storage';\nimport * as Media from '@/util/media';\nimport * as OpenAI from '@/util/openai';\nimport { stringifyJSON } from '@/util/general';\nimport path from 'path';\nimport { ChatCompletionMessageParam } from 'openai/resources';\nimport { Chat, Formatter } from '@riotprompt/riotprompt';\nimport * as TranscribePrompt from '@/prompt/transcribe';\n\nexport interface Transcription {\n text: string;\n audioFileBasename: string;\n}\n\nexport interface Instance {\n transcribe: (creation: Date, outputPath: string, contextPath: string, interimPath: string, filename: string, hash: string, audioFile: string) => Promise<Transcription>;\n}\n\nexport const create = (config: Config, operator: Dreadcabinet.Operator): Instance => {\n const logger = Logging.getLogger();\n const storage = Storage.create({ log: logger.debug });\n const media = Media.create(logger);\n const prompts = TranscribePrompt.create(config.model as Chat.Model, config);\n\n const transcribe = async (creation: Date, outputPath: string, contextPath: string, interimPath: string, filename: string, hash: string, audioFile: string): Promise<Transcription> => {\n if (!outputPath) {\n throw new Error(\"outputPath is required for transcribe function\");\n }\n\n if (!audioFile) {\n throw new Error(\"audioFile is required for transcribe function\");\n }\n\n // Remove extension from audioFile and make the name filesafe\n const audioFileBasename = path.basename(audioFile, path.extname(audioFile))\n .replace(/[^a-zA-Z0-9_-]/g, '_') // Replace non-alphanumeric chars with underscore\n .replace(/_+/g, '_') // Replace multiple underscores with a single one\n .trim();\n\n logger.debug(`Processed audio filename: ${audioFileBasename}`);\n\n let transcriptOutputFilename = await operator.constructFilename(creation, 'transcript', hash, { subject: audioFileBasename });\n // Ensure the filename ends with .json\n if (!transcriptOutputFilename.endsWith('.json')) {\n logger.warn('constructFilename did not return a .json file for transcript, appending extension: %s', transcriptOutputFilename);\n transcriptOutputFilename += '.json';\n }\n\n const transcriptOutputPath = path.join(interimPath, transcriptOutputFilename);\n\n // Check if transcription already exists\n if (await storage.exists(transcriptOutputPath)) {\n logger.info('Transcription file %s already exists, returning existing content...', transcriptOutputPath);\n const existingContent = await storage.readFile(transcriptOutputPath, 'utf8');\n return JSON.parse(existingContent);\n }\n\n const baseDebugFilename = path.parse(transcriptOutputFilename).name;\n const transcriptionDebugFile = config.debug ? path.join(interimPath, `${baseDebugFilename}.transcription.raw.response.json`) : undefined;\n\n // Check if audio file exceeds the size limit\n const fileSize = await media.getFileSize(audioFile);\n logger.debug(`Audio file size: ${fileSize} bytes, max size: ${config.maxAudioSize} bytes`);\n\n let transcription: OpenAI.Transcription;\n\n if (fileSize > config.maxAudioSize) {\n logger.info(`Audio file exceeds maximum size (${fileSize} > ${config.maxAudioSize} bytes), splitting into chunks`);\n\n // Create a temporary directory for the audio chunks\n const tempDir = path.join(config.tempDirectory, `split_audio_${hash}`);\n await storage.createDirectory(tempDir);\n\n try {\n // Split the audio file into chunks\n const audioChunks = await media.splitAudioFile(audioFile, tempDir, config.maxAudioSize);\n logger.info(`Split audio file into ${audioChunks.length} chunks`);\n\n // Transcribe each chunk\n const transcriptions: OpenAI.Transcription[] = [];\n for (let i = 0; i < audioChunks.length; i++) {\n const chunkPath = audioChunks[i];\n logger.info(`Transcribing chunk ${i + 1}/${audioChunks.length}: ${chunkPath}`);\n\n const chunkDebugFile = config.debug ?\n path.join(interimPath, `${baseDebugFilename}.transcription.chunk${i + 1}.raw.response.json`) :\n undefined;\n\n const chunkTranscription = await OpenAI.transcribeAudio(chunkPath, {\n model: config.transcriptionModel,\n debug: config.debug,\n debugFile: chunkDebugFile\n });\n\n transcriptions.push(chunkTranscription);\n }\n\n // Combine all transcriptions\n const combinedText = transcriptions.map(t => t.text).join(' ');\n transcription = { text: combinedText };\n\n // Save each individual chunk for debugging\n await storage.writeFile(\n path.join(interimPath, `${baseDebugFilename}.transcription.combined.json`),\n stringifyJSON({ chunks: transcriptions, combined: transcription }),\n 'utf8'\n );\n\n // Clean up temporary files if not in debug mode\n if (!config.debug) {\n for (const chunk of audioChunks) {\n try {\n await storage.deleteFile(chunk);\n } catch (error) {\n logger.warn(`Failed to delete temporary chunk ${chunk}: ${error}`);\n }\n }\n }\n } catch (error) {\n logger.error(`Error processing split audio files: ${error}`);\n throw new Error(`Failed to process split audio files: ${error}`);\n }\n } else {\n // If file size is within the limit, transcribe normally\n transcription = await OpenAI.transcribeAudio(audioFile, {\n model: config.transcriptionModel,\n debug: config.debug,\n debugFile: transcriptionDebugFile\n });\n }\n\n // Save the transcription\n await storage.writeFile(transcriptOutputPath, stringifyJSON(transcription), 'utf8');\n logger.debug('Wrote transcription to %s', transcriptOutputPath);\n\n // Create markdown version of the transcript\n const markdownOutputFilename = transcriptOutputFilename.replace('.json', '.md');\n const markdownOutputPath = path.join(outputPath, markdownOutputFilename);\n\n // Only create the markdown file if it doesn't already exist\n if (!await storage.exists(markdownOutputPath)) {\n logger.info('Creating Markdown version of the transcription...');\n\n // Create a prompt for the transcription formatting task\n const prompt = await prompts.createTranscribePrompt(transcription.text);\n\n // Format the prompt using the override utility\n const formatter = Formatter.create();\n const chatRequest: Chat.Request = formatter.formatPrompt(config.model as Chat.Model, prompt);\n\n // Debug file paths for the request and response\n const requestDebugFile = config.debug ?\n path.join(interimPath, `${baseDebugFilename}.markdown.request.json`) :\n undefined;\n\n const responseDebugFile = config.debug ?\n path.join(interimPath, `${baseDebugFilename}.markdown.response.json`) :\n undefined;\n\n // Write debug file for the request if in debug mode\n if (config.debug && requestDebugFile) {\n await storage.writeFile(requestDebugFile, stringifyJSON(chatRequest), 'utf8');\n logger.debug('Wrote chat request to %s', requestDebugFile);\n }\n\n // Call the model to convert the transcription to markdown\n const markdownContent = await OpenAI.createCompletion(\n chatRequest.messages as ChatCompletionMessageParam[],\n {\n model: config.model,\n debug: config.debug,\n debugFile: responseDebugFile\n }\n );\n\n // Save the markdown version\n await storage.writeFile(markdownOutputPath, markdownContent, 'utf8');\n logger.debug('Wrote markdown transcription to %s', markdownOutputPath);\n } else {\n logger.info('Markdown transcription file %s already exists, skipping...', markdownOutputPath);\n }\n\n return {\n ...transcription,\n audioFileBasename,\n };\n }\n\n return {\n transcribe,\n }\n} "],"names":["create","config","operator","logger","Logging","storage","Storage","log","debug","media","Media","prompts","TranscribePrompt","model","transcribe","creation","outputPath","contextPath","interimPath","filename","hash","audioFile","Error","audioFileBasename","path","basename","extname","replace","trim","transcriptOutputFilename","constructFilename","subject","endsWith","warn","transcriptOutputPath","join","exists","info","existingContent","readFile","JSON","parse","baseDebugFilename","name","transcriptionDebugFile","undefined","fileSize","getFileSize","maxAudioSize","transcription","tempDir","tempDirectory","createDirectory","audioChunks","splitAudioFile","length","transcriptions","i","chunkPath","chunkDebugFile","chunkTranscription","OpenAI","transcriptionModel","debugFile","push","combinedText","map","t","text","writeFile","stringifyJSON","chunks","combined","chunk","deleteFile","error","markdownOutputFilename","markdownOutputPath","prompt","createTranscribePrompt","formatter","Formatter","chatRequest","formatPrompt","requestDebugFile","responseDebugFile","markdownContent","messages"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getLogger } from './logging.js';
|
|
2
|
+
import { create as create$1 } from './phases/transcribe.js';
|
|
3
|
+
import { create as create$2 } from './phases/locate.js';
|
|
4
|
+
|
|
5
|
+
const create = (config, operator)=>{
|
|
6
|
+
const logger = getLogger();
|
|
7
|
+
const transcribePhase = create$1(config, operator);
|
|
8
|
+
const locatePhase = create$2(config, operator);
|
|
9
|
+
const process = async (audioFile)=>{
|
|
10
|
+
logger.verbose('Processing file %s', audioFile);
|
|
11
|
+
// Locate the contents in time and on the filesystem
|
|
12
|
+
logger.debug('Locating file %s', audioFile);
|
|
13
|
+
const { creationTime, outputPath, contextPath, interimPath, transcriptionFilename, hash } = await locatePhase.locate(audioFile);
|
|
14
|
+
logger.debug('Locate complete: %s', JSON.stringify({
|
|
15
|
+
creationTime,
|
|
16
|
+
outputPath,
|
|
17
|
+
contextPath,
|
|
18
|
+
interimPath,
|
|
19
|
+
transcriptionFilename,
|
|
20
|
+
hash
|
|
21
|
+
}));
|
|
22
|
+
// Transcribe the audio
|
|
23
|
+
logger.debug('Transcribing file %s', audioFile);
|
|
24
|
+
await transcribePhase.transcribe(creationTime, outputPath, contextPath, interimPath, transcriptionFilename, hash, audioFile);
|
|
25
|
+
logger.info('Transcription complete for file %s', audioFile);
|
|
26
|
+
logger.info('Transcription saved to: %s', transcriptionFilename);
|
|
27
|
+
return;
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
process
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { create };
|
|
35
|
+
//# sourceMappingURL=processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor.js","sources":["../src/processor.ts"],"sourcesContent":["import * as Logging from '@/logging';\nimport * as TranscribePhase from '@/phases/transcribe';\nimport * as LocatePhase from '@/phases/locate';\nimport * as Dreadcabinet from '@theunwalked/dreadcabinet';\nimport { Config } from '@/protokoll';\n\nexport interface Transcription {\n text: string;\n audioFileBasename: string;\n}\n\nexport interface Instance {\n process(file: string): Promise<void>;\n}\n\nexport const create = (config: Config, operator: Dreadcabinet.Operator): Instance => {\n const logger = Logging.getLogger();\n\n const transcribePhase: TranscribePhase.Instance = TranscribePhase.create(config, operator);\n const locatePhase: LocatePhase.Instance = LocatePhase.create(config, operator);\n\n const process = async (audioFile: string) => {\n logger.verbose('Processing file %s', audioFile);\n\n // Locate the contents in time and on the filesystem\n logger.debug('Locating file %s', audioFile);\n const { creationTime, outputPath, contextPath, interimPath, transcriptionFilename, hash } = await locatePhase.locate(audioFile);\n logger.debug('Locate complete: %s', JSON.stringify({ creationTime, outputPath, contextPath, interimPath, transcriptionFilename, hash }));\n\n // Transcribe the audio\n logger.debug('Transcribing file %s', audioFile);\n await transcribePhase.transcribe(creationTime, outputPath, contextPath, interimPath, transcriptionFilename, hash, audioFile);\n\n logger.info('Transcription complete for file %s', audioFile);\n logger.info('Transcription saved to: %s', transcriptionFilename);\n return;\n }\n\n return {\n process,\n }\n}\n\n\n"],"names":["create","config","operator","logger","Logging","transcribePhase","TranscribePhase","locatePhase","LocatePhase","process","audioFile","verbose","debug","creationTime","outputPath","contextPath","interimPath","transcriptionFilename","hash","locate","JSON","stringify","transcribe","info"],"mappings":";;;;AAeO,MAAMA,MAAAA,GAAS,CAACC,MAAAA,EAAgBC,QAAAA,GAAAA;IACnC,MAAMC,MAAAA,GAASC,SAAiB,EAAA;AAEhC,IAAA,MAAMC,eAAAA,GAA4CC,QAAsB,CAACL,MAAAA,EAAQC,QAAAA,CAAAA;AACjF,IAAA,MAAMK,WAAAA,GAAoCC,QAAkB,CAACP,MAAAA,EAAQC,QAAAA,CAAAA;AAErE,IAAA,MAAMO,UAAU,OAAOC,SAAAA,GAAAA;QACnBP,MAAAA,CAAOQ,OAAO,CAAC,oBAAA,EAAsBD,SAAAA,CAAAA;;QAGrCP,MAAAA,CAAOS,KAAK,CAAC,kBAAA,EAAoBF,SAAAA,CAAAA;AACjC,QAAA,MAAM,EAAEG,YAAY,EAAEC,UAAU,EAAEC,WAAW,EAAEC,WAAW,EAAEC,qBAAqB,EAAEC,IAAI,EAAE,GAAG,MAAMX,WAAAA,CAAYY,MAAM,CAACT,SAAAA,CAAAA;AACrHP,QAAAA,MAAAA,CAAOS,KAAK,CAAC,qBAAA,EAAuBQ,IAAAA,CAAKC,SAAS,CAAC;AAAER,YAAAA,YAAAA;AAAcC,YAAAA,UAAAA;AAAYC,YAAAA,WAAAA;AAAaC,YAAAA,WAAAA;AAAaC,YAAAA,qBAAAA;AAAuBC,YAAAA;AAAK,SAAA,CAAA,CAAA;;QAGrIf,MAAAA,CAAOS,KAAK,CAAC,sBAAA,EAAwBF,SAAAA,CAAAA;QACrC,MAAML,eAAAA,CAAgBiB,UAAU,CAACT,YAAAA,EAAcC,YAAYC,WAAAA,EAAaC,WAAAA,EAAaC,uBAAuBC,IAAAA,EAAMR,SAAAA,CAAAA;QAElHP,MAAAA,CAAOoB,IAAI,CAAC,oCAAA,EAAsCb,SAAAA,CAAAA;QAClDP,MAAAA,CAAOoB,IAAI,CAAC,4BAAA,EAA8BN,qBAAAA,CAAAA;AAC1C,QAAA;AACJ,IAAA,CAAA;IAEA,OAAO;AACHR,QAAAA;AACJ,KAAA;AACJ;;;;"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
## 🧠 Whisper Transcript Post-Processor Prompt
|
|
2
|
+
|
|
3
|
+
You're a transcript formatting tool, not a summarizer, editor, or rewriter.
|
|
4
|
+
|
|
5
|
+
You will receive a raw transcript from Whisper. Your task is to convert it into **clean, readable Markdown** with **intelligent paragraph breaks**, optional **section headings**, and accurate spelling of names and concepts.
|
|
6
|
+
|
|
7
|
+
### 📝 Output Format
|
|
8
|
+
|
|
9
|
+
- Output **MUST be in Markdown**.
|
|
10
|
+
- Use **`#`, `##`, or `###` headings** to group content into logical sections **if** a topic shift is clearly identifiable.
|
|
11
|
+
- Insert **paragraph breaks** in appropriate places to improve readability, particularly:
|
|
12
|
+
- at topic shifts
|
|
13
|
+
- after long pauses or asides
|
|
14
|
+
- between distinct ideas
|
|
15
|
+
- Do **not** summarize, shorten, or omit anything unless it's clearly repetitive or a verbal filler (e.g. "uh", "you know", "like" used in isolation).
|
|
16
|
+
- Do **not** embellish or market language. For example, do not rephrase “this might work” as “this innovative idea…”
|
|
17
|
+
|
|
18
|
+
### 🔍 Fidelity Requirements
|
|
19
|
+
|
|
20
|
+
- **Do not simplify or reinterpret the speaker’s intent.**
|
|
21
|
+
- Do not remove technical details, curse words, or hedged or tentative phrasing.
|
|
22
|
+
- Preserve filler words **if they contribute to tone or meaning** (e.g. “I mean”, “sort of”, “well”), but collapse **repetitions** of the exact same phrase if clearly unintentional.
|
|
23
|
+
|
|
24
|
+
### ✏️ Spelling & Entity Correction
|
|
25
|
+
|
|
26
|
+
Use the **provided context** (e.g. glossary, list of names, known topics) to:
|
|
27
|
+
|
|
28
|
+
- Correct spelling of **people's names**, **company names**, **tools**, or **technical terms** that Whisper might get wrong.
|
|
29
|
+
- Example: if the context includes “Adrian Sloan” and the transcript says “Adreean Slohn”, correct it to “Adrian Sloan”.
|
|
30
|
+
|
|
31
|
+
If you are uncertain about a correction, include the likely correct term with the original in parentheses:
|
|
32
|
+
e.g. `Adrian Sloan (transcript: "Adreean Slohn")`
|
|
33
|
+
|
|
34
|
+
### 🚫 Do Not:
|
|
35
|
+
|
|
36
|
+
- Do not shorten the transcript.
|
|
37
|
+
- Do not summarize.
|
|
38
|
+
- Do not interpret tone or intent.
|
|
39
|
+
- Do not turn notes into copy.
|
|
40
|
+
- Do not hallucinate or "fix" awkward phrasing unless it's an obvious transcription error.
|
|
41
|
+
|
|
42
|
+
### ✅ Do:
|
|
43
|
+
|
|
44
|
+
- Maintain all nuance.
|
|
45
|
+
- Correct mistranscribed words or names using context.
|
|
46
|
+
- Output clean, readable Markdown for humans or downstream systems to use.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
## 🧑💼 Persona: TranscriptFormatter-v1
|
|
2
|
+
|
|
3
|
+
**Role**: Markdown transcript formatter and error-correction agent.
|
|
4
|
+
|
|
5
|
+
**Purpose**: To convert raw, unstructured Whisper-generated transcripts into structured, readable, and **high-fidelity** Markdown documents suitable for human review or downstream system processing.
|
|
6
|
+
|
|
7
|
+
### 🎯 Core Traits
|
|
8
|
+
|
|
9
|
+
- **Literal** – Captures exactly what was said, not what *should have* been said.
|
|
10
|
+
- **Structured** – Organizes content with paragraphs and optional section headings without altering meaning.
|
|
11
|
+
- **Context-aware** – Uses external context to resolve proper names, technical terms, and common transcription errors.
|
|
12
|
+
- **Anti-summarizer** – Never reduces, condenses, or editorializes.
|
|
13
|
+
- **Language-fidelity obsessed** – Preserves the tone, hesitations, repetitions (unless clearly unintentional), and casual phrasing.
|
|
14
|
+
|
|
15
|
+
### 🧱 Boundaries
|
|
16
|
+
|
|
17
|
+
- Will **not** reword awkward phrasing for style.
|
|
18
|
+
- Will **not** remove profanity, hedging, or emotion unless explicitly instructed.
|
|
19
|
+
- Will **not** guess or extrapolate beyond context or transcript.
|
|
20
|
+
|
|
21
|
+
### 🧰 Toolkit
|
|
22
|
+
|
|
23
|
+
- Markdown formatting engine (headings, paragraphs, emphasis).
|
|
24
|
+
- Entity correction using supplied glossary/context.
|
|
25
|
+
- Repetition collapse (only when verbatim duplication is evident).
|
|
26
|
+
- Parenthetical disambiguation when corrections are uncertain.
|
|
27
|
+
|
|
28
|
+
### ✅ Example Behavior
|
|
29
|
+
|
|
30
|
+
- Transcript says: `"uh we talked to adreean slohn yesterday about the update thing"`
|
|
31
|
+
- Context lists: `Adrian Sloan`
|
|
32
|
+
- Output:
|
|
33
|
+
`"We talked to Adrian Sloan (transcript: "adreean slohn") yesterday about the update thing."`
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
*TranscriptFormatter-v1 is not a creative assistant. It is a high-accuracy Markdown transcription formatter trained to obey literal constraints and structural cues.*
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Builder } from '@riotprompt/riotprompt';
|
|
2
|
+
import { DEFAULT_PERSONA_TRANSCRIBER_FILE, DEFAULT_INSTRUCTIONS_TRANSCRIBE_FILE } from '../constants.js';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { getLogger } from '../logging.js';
|
|
6
|
+
|
|
7
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
9
|
+
/**
|
|
10
|
+
* Creates a prompt for the transcription formatting task
|
|
11
|
+
*/ const createTranscribePrompt = async (transcriptionText, config)=>{
|
|
12
|
+
const logger = getLogger();
|
|
13
|
+
let builder = Builder.create({
|
|
14
|
+
logger,
|
|
15
|
+
basePath: __dirname$1,
|
|
16
|
+
overridePaths: [
|
|
17
|
+
config.configDirectory
|
|
18
|
+
],
|
|
19
|
+
overrides: config.overrides
|
|
20
|
+
});
|
|
21
|
+
builder = await builder.addPersonaPath(DEFAULT_PERSONA_TRANSCRIBER_FILE);
|
|
22
|
+
builder = await builder.addInstructionPath(DEFAULT_INSTRUCTIONS_TRANSCRIBE_FILE);
|
|
23
|
+
builder = await builder.addContent(transcriptionText);
|
|
24
|
+
if (config.contextDirectories) {
|
|
25
|
+
builder = await builder.loadContext(config.contextDirectories);
|
|
26
|
+
}
|
|
27
|
+
const prompt = await builder.build();
|
|
28
|
+
return prompt;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Create a factory for transcribe prompts
|
|
32
|
+
*/ const create = (model, config)=>{
|
|
33
|
+
return {
|
|
34
|
+
createTranscribePrompt: async (transcriptionText)=>{
|
|
35
|
+
return createTranscribePrompt(transcriptionText, config);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { create, createTranscribePrompt };
|
|
41
|
+
//# sourceMappingURL=transcribe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcribe.js","sources":["../../src/prompt/transcribe.ts"],"sourcesContent":["import { Builder, Chat, Prompt } from \"@riotprompt/riotprompt\";\nimport { DEFAULT_INSTRUCTIONS_TRANSCRIBE_FILE, DEFAULT_PERSONA_TRANSCRIBER_FILE } from '@/constants';\nimport { Config } from '@/protokoll';\nimport { fileURLToPath } from \"url\";\nimport path from \"path\";\nimport { getLogger } from \"@/logging\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Creates a prompt for the transcription formatting task\n */\nexport const createTranscribePrompt = async (\n transcriptionText: string,\n config: Config\n): Promise<Prompt> => {\n const logger = getLogger();\n let builder: Builder.Instance = Builder.create({ logger, basePath: __dirname, overridePaths: [config.configDirectory], overrides: config.overrides });\n builder = await builder.addPersonaPath(DEFAULT_PERSONA_TRANSCRIBER_FILE);\n builder = await builder.addInstructionPath(DEFAULT_INSTRUCTIONS_TRANSCRIBE_FILE);\n builder = await builder.addContent(transcriptionText);\n if (config.contextDirectories) {\n builder = await builder.loadContext(config.contextDirectories);\n }\n\n const prompt = await builder.build();\n return prompt;\n};\n\n/**\n * Factory interface for transcribe prompts\n */\nexport interface Factory {\n createTranscribePrompt: (transcriptionText: string) => Promise<Prompt>;\n}\n\n/**\n * Create a factory for transcribe prompts\n */\nexport const create = (model: Chat.Model, config: Config): Factory => {\n return {\n createTranscribePrompt: async (transcriptionText: string): Promise<Prompt> => {\n return createTranscribePrompt(transcriptionText, config);\n }\n };\n}; "],"names":["__filename","fileURLToPath","url","__dirname","path","dirname","createTranscribePrompt","transcriptionText","config","logger","getLogger","builder","Builder","create","basePath","overridePaths","configDirectory","overrides","addPersonaPath","DEFAULT_PERSONA_TRANSCRIBER_FILE","addInstructionPath","DEFAULT_INSTRUCTIONS_TRANSCRIBE_FILE","addContent","contextDirectories","loadContext","prompt","build","model"],"mappings":";;;;;;AAOA,MAAMA,YAAAA,GAAaC,aAAAA,CAAc,MAAA,CAAA,IAAA,CAAYC,GAAG,CAAA;AAChD,MAAMC,WAAAA,GAAYC,IAAAA,CAAKC,OAAO,CAACL,YAAAA,CAAAA;AAE/B;;AAEC,IACM,MAAMM,sBAAAA,GAAyB,OAClCC,iBAAAA,EACAC,MAAAA,GAAAA;AAEA,IAAA,MAAMC,MAAAA,GAASC,SAAAA,EAAAA;IACf,IAAIC,OAAAA,GAA4BC,OAAAA,CAAQC,MAAM,CAAC;AAAEJ,QAAAA,MAAAA;QAAQK,QAAAA,EAAUX,WAAAA;QAAWY,aAAAA,EAAe;AAACP,YAAAA,MAAAA,CAAOQ;AAAgB,SAAA;AAAEC,QAAAA,SAAAA,EAAWT,OAAOS;AAAU,KAAA,CAAA;IACnJN,OAAAA,GAAU,MAAMA,OAAAA,CAAQO,cAAc,CAACC,gCAAAA,CAAAA;IACvCR,OAAAA,GAAU,MAAMA,OAAAA,CAAQS,kBAAkB,CAACC,oCAAAA,CAAAA;IAC3CV,OAAAA,GAAU,MAAMA,OAAAA,CAAQW,UAAU,CAACf,iBAAAA,CAAAA;IACnC,IAAIC,MAAAA,CAAOe,kBAAkB,EAAE;AAC3BZ,QAAAA,OAAAA,GAAU,MAAMA,OAAAA,CAAQa,WAAW,CAAChB,OAAOe,kBAAkB,CAAA;AACjE,IAAA;IAEA,MAAME,MAAAA,GAAS,MAAMd,OAAAA,CAAQe,KAAK,EAAA;IAClC,OAAOD,MAAAA;AACX;AASA;;AAEC,IACM,MAAMZ,MAAAA,GAAS,CAACc,KAAAA,EAAmBnB,MAAAA,GAAAA;IACtC,OAAO;AACHF,QAAAA,sBAAAA,EAAwB,OAAOC,iBAAAA,GAAAA;AAC3B,YAAA,OAAOD,uBAAuBC,iBAAAA,EAAmBC,MAAAA,CAAAA;AACrD,QAAA;AACJ,KAAA;AACJ;;;;"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import { configure } from './arguments.js';
|
|
4
|
+
import { PROGRAM_NAME, VERSION, ALLOWED_OUTPUT_FILENAME_OPTIONS, ALLOWED_OUTPUT_STRUCTURES, ALLOWED_AUDIO_EXTENSIONS, DEFAULT_OUTPUT_DIRECTORY, DEFAULT_INPUT_DIRECTORY, DEFAULT_OUTPUT_FILENAME_OPTIONS, DEFAULT_OUTPUT_STRUCTURE, DEFAULT_AUDIO_EXTENSIONS, DEFAULT_TIMEZONE, DEFAULT_CONFIG_DIR } from './constants.js';
|
|
5
|
+
import { setLogLevel, getLogger } from './logging.js';
|
|
6
|
+
import { create } from './processor.js';
|
|
7
|
+
import * as Dreadcabinet from '@theunwalked/dreadcabinet';
|
|
8
|
+
import * as Cardigantime from '@theunwalked/cardigantime';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
const ConfigSchema = z.object({
|
|
12
|
+
dryRun: z.boolean(),
|
|
13
|
+
verbose: z.boolean(),
|
|
14
|
+
debug: z.boolean(),
|
|
15
|
+
diff: z.boolean(),
|
|
16
|
+
log: z.boolean(),
|
|
17
|
+
model: z.string(),
|
|
18
|
+
transcriptionModel: z.string(),
|
|
19
|
+
contentTypes: z.array(z.string()),
|
|
20
|
+
overrides: z.boolean(),
|
|
21
|
+
contextDirectories: z.array(z.string()).optional(),
|
|
22
|
+
maxAudioSize: z.number(),
|
|
23
|
+
tempDirectory: z.string()
|
|
24
|
+
});
|
|
25
|
+
z.object({
|
|
26
|
+
openaiApiKey: z.string().optional()
|
|
27
|
+
});
|
|
28
|
+
async function main() {
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.info(`Starting ${PROGRAM_NAME}: ${VERSION}`);
|
|
31
|
+
const dreadcabinetOptions = {
|
|
32
|
+
defaults: {
|
|
33
|
+
timezone: DEFAULT_TIMEZONE,
|
|
34
|
+
extensions: DEFAULT_AUDIO_EXTENSIONS,
|
|
35
|
+
outputStructure: DEFAULT_OUTPUT_STRUCTURE,
|
|
36
|
+
outputFilenameOptions: DEFAULT_OUTPUT_FILENAME_OPTIONS,
|
|
37
|
+
inputDirectory: DEFAULT_INPUT_DIRECTORY,
|
|
38
|
+
outputDirectory: DEFAULT_OUTPUT_DIRECTORY
|
|
39
|
+
},
|
|
40
|
+
allowed: {
|
|
41
|
+
extensions: ALLOWED_AUDIO_EXTENSIONS,
|
|
42
|
+
outputStructures: ALLOWED_OUTPUT_STRUCTURES,
|
|
43
|
+
outputFilenameOptions: ALLOWED_OUTPUT_FILENAME_OPTIONS
|
|
44
|
+
},
|
|
45
|
+
features: Dreadcabinet.DEFAULT_FEATURES,
|
|
46
|
+
addDefaults: false
|
|
47
|
+
};
|
|
48
|
+
const dreadcabinet = Dreadcabinet.create(dreadcabinetOptions);
|
|
49
|
+
const cardigantime = Cardigantime.create({
|
|
50
|
+
defaults: {
|
|
51
|
+
configDirectory: DEFAULT_CONFIG_DIR
|
|
52
|
+
},
|
|
53
|
+
configShape: ConfigSchema.shape
|
|
54
|
+
});
|
|
55
|
+
const [config, secureConfig] = await configure(dreadcabinet, cardigantime);
|
|
56
|
+
// Set log level based on verbose flag
|
|
57
|
+
if (config.verbose === true) {
|
|
58
|
+
setLogLevel('verbose');
|
|
59
|
+
}
|
|
60
|
+
if (config.debug === true) {
|
|
61
|
+
setLogLevel('debug');
|
|
62
|
+
}
|
|
63
|
+
const logger = getLogger();
|
|
64
|
+
dreadcabinet.setLogger(logger);
|
|
65
|
+
try {
|
|
66
|
+
const operator = await dreadcabinet.operate({
|
|
67
|
+
...config,
|
|
68
|
+
...secureConfig
|
|
69
|
+
});
|
|
70
|
+
const processor = create(config, operator);
|
|
71
|
+
await operator.process(async (file)=>{
|
|
72
|
+
await processor.process(file);
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
logger.error('Exiting due to Error: %s, %s', error.message, error.stack);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { ConfigSchema, main };
|
|
81
|
+
//# sourceMappingURL=protokoll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protokoll.js","sources":["../src/protokoll.ts"],"sourcesContent":["#!/usr/bin/env node\nimport 'dotenv/config';\nimport * as Arguments from '@/arguments';\nimport { ALLOWED_AUDIO_EXTENSIONS, ALLOWED_OUTPUT_FILENAME_OPTIONS, ALLOWED_OUTPUT_STRUCTURES, DEFAULT_AUDIO_EXTENSIONS, DEFAULT_OUTPUT_FILENAME_OPTIONS, DEFAULT_INPUT_DIRECTORY, DEFAULT_OUTPUT_DIRECTORY, DEFAULT_OUTPUT_STRUCTURE, DEFAULT_TIMEZONE, PROGRAM_NAME, VERSION, DEFAULT_CONFIG_DIR } from '@/constants';\nimport { getLogger, setLogLevel } from '@/logging';\nimport * as Processor from './processor';\nimport * as Dreadcabinet from '@theunwalked/dreadcabinet';\nimport * as Cardigantime from '@theunwalked/cardigantime';\nimport { z } from 'zod';\n\nexport interface Args extends Dreadcabinet.Args, Cardigantime.Args {\n dryRun?: boolean;\n verbose?: boolean;\n debug?: boolean;\n transcriptionModel?: string;\n model?: string;\n openaiApiKey?: string;\n overrides?: boolean;\n contextDirectories?: string[];\n maxAudioSize?: number | string;\n tempDirectory?: string;\n}\n\nexport const ConfigSchema = z.object({\n dryRun: z.boolean(),\n verbose: z.boolean(),\n debug: z.boolean(),\n diff: z.boolean(),\n log: z.boolean(),\n model: z.string(),\n transcriptionModel: z.string(),\n contentTypes: z.array(z.string()),\n overrides: z.boolean(),\n contextDirectories: z.array(z.string()).optional(),\n maxAudioSize: z.number(),\n tempDirectory: z.string(),\n});\n\nexport const SecureConfigSchema = z.object({\n openaiApiKey: z.string().optional(),\n});\n\nexport type Config = z.infer<typeof ConfigSchema> & Dreadcabinet.Config & Cardigantime.Config;\nexport type SecureConfig = z.infer<typeof SecureConfigSchema>;\n\nexport async function main() {\n\n // eslint-disable-next-line no-console\n console.info(`Starting ${PROGRAM_NAME}: ${VERSION}`);\n\n const dreadcabinetOptions = {\n defaults: {\n timezone: DEFAULT_TIMEZONE,\n extensions: DEFAULT_AUDIO_EXTENSIONS,\n outputStructure: DEFAULT_OUTPUT_STRUCTURE,\n outputFilenameOptions: DEFAULT_OUTPUT_FILENAME_OPTIONS,\n inputDirectory: DEFAULT_INPUT_DIRECTORY,\n outputDirectory: DEFAULT_OUTPUT_DIRECTORY,\n },\n allowed: {\n extensions: ALLOWED_AUDIO_EXTENSIONS,\n outputStructures: ALLOWED_OUTPUT_STRUCTURES,\n outputFilenameOptions: ALLOWED_OUTPUT_FILENAME_OPTIONS,\n },\n features: Dreadcabinet.DEFAULT_FEATURES,\n addDefaults: false,\n };\n\n const dreadcabinet = Dreadcabinet.create(dreadcabinetOptions);\n\n const cardigantime = Cardigantime.create({\n defaults: {\n configDirectory: DEFAULT_CONFIG_DIR,\n },\n configShape: ConfigSchema.shape,\n });\n\n const [config, secureConfig]: [Config, SecureConfig] = await Arguments.configure(dreadcabinet, cardigantime);\n\n // Set log level based on verbose flag\n if (config.verbose === true) {\n setLogLevel('verbose');\n }\n if (config.debug === true) {\n setLogLevel('debug');\n }\n\n const logger = getLogger();\n dreadcabinet.setLogger(logger);\n\n try {\n\n const operator: Dreadcabinet.Operator = await dreadcabinet.operate({\n ...config,\n ...secureConfig,\n });\n const processor = Processor.create(config, operator);\n\n await operator.process(async (file: string) => {\n await processor.process(file);\n });\n } catch (error: any) {\n logger.error('Exiting due to Error: %s, %s', error.message, error.stack);\n process.exit(1);\n }\n}"],"names":["ConfigSchema","z","object","dryRun","boolean","verbose","debug","diff","log","model","string","transcriptionModel","contentTypes","array","overrides","contextDirectories","optional","maxAudioSize","number","tempDirectory","openaiApiKey","main","console","info","PROGRAM_NAME","VERSION","dreadcabinetOptions","defaults","timezone","DEFAULT_TIMEZONE","extensions","DEFAULT_AUDIO_EXTENSIONS","outputStructure","DEFAULT_OUTPUT_STRUCTURE","outputFilenameOptions","DEFAULT_OUTPUT_FILENAME_OPTIONS","inputDirectory","DEFAULT_INPUT_DIRECTORY","outputDirectory","DEFAULT_OUTPUT_DIRECTORY","allowed","ALLOWED_AUDIO_EXTENSIONS","outputStructures","ALLOWED_OUTPUT_STRUCTURES","ALLOWED_OUTPUT_FILENAME_OPTIONS","features","Dreadcabinet","DEFAULT_FEATURES","addDefaults","dreadcabinet","create","cardigantime","Cardigantime","configDirectory","DEFAULT_CONFIG_DIR","configShape","shape","config","secureConfig","Arguments","setLogLevel","logger","getLogger","setLogger","operator","operate","processor","Processor","process","file","error","message","stack","exit"],"mappings":"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"}
|