@tyvm/knowhow 0.0.90 → 0.0.92
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/.depcheckrc +30 -0
- package/bin/knowhow.js +1 -1
- package/package.json +8 -34
- package/src/agents/configurable/ConfigAgent.ts +2 -2
- package/src/agents/tools/executeScript/index.ts +5 -0
- package/src/agents/tools/googleSearch.ts +2 -2
- package/src/agents/tools/index.ts +0 -3
- package/src/agents/tools/list.ts +0 -147
- package/src/agents/tools/loadWebpage.ts +3 -113
- package/src/auth/browserLogin.ts +10 -13
- package/src/chat/modules/AgentModule.ts +0 -1
- package/src/chat/types.ts +1 -1
- package/src/cli.ts +63 -3
- package/src/clients/gemini.ts +96 -25
- package/src/clients/http.ts +7 -11
- package/src/clients/pricing/google.ts +122 -26
- package/src/conversion.ts +24 -54
- package/src/index.ts +15 -20
- package/src/login.ts +5 -6
- package/src/plugins/language.ts +0 -4
- package/src/plugins/plugins.ts +0 -14
- package/src/plugins/url.ts +31 -12
- package/src/services/EmbeddingsService.ts +70 -0
- package/src/services/KnowhowClient.ts +34 -34
- package/src/{plugins/downloader/downloader.ts → services/MediaProcessorService.ts} +109 -267
- package/src/services/S3.ts +19 -87
- package/src/services/index.ts +8 -8
- package/src/services/modules/index.ts +12 -3
- package/src/services/modules/types.ts +8 -2
- package/src/services/script-execution/ScriptExecutor.ts +29 -10
- package/src/services/script-execution/ScriptPolicy.ts +6 -2
- package/src/types.ts +1 -0
- package/src/utils/http.ts +127 -0
- package/src/workers/auth/PasskeySetup.ts +7 -11
- package/tests/clients/AIClient.test.ts +24 -21
- package/tests/manual/file-edits/figma.test.ts +3 -70
- package/tests/plugins/language/languagePlugin-content-triggers.test.ts +2 -0
- package/tests/plugins/language/languagePlugin.test.ts +2 -0
- package/tests/processors/ToolResponseCache.test.ts +2 -2
- package/tests/test.spec.ts +0 -14
- package/tests/unit/modules/moduleLoading.test.ts +12 -4
- package/tests/unit/plugins/pluginLoading.test.ts +6 -6
- package/ts_build/package.json +8 -34
- package/ts_build/src/agents/tools/ast/astAppendNode.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astAppendNode.js +2 -90
- package/ts_build/src/agents/tools/ast/astAppendNode.js.map +1 -1
- package/ts_build/src/agents/tools/ast/astDeleteNode.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astDeleteNode.js +2 -88
- package/ts_build/src/agents/tools/ast/astDeleteNode.js.map +1 -1
- package/ts_build/src/agents/tools/ast/astEditNode.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astEditNode.js +2 -90
- package/ts_build/src/agents/tools/ast/astEditNode.js.map +1 -1
- package/ts_build/src/agents/tools/ast/astGetPathForLine.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astGetPathForLine.js +2 -72
- package/ts_build/src/agents/tools/ast/astGetPathForLine.js.map +1 -1
- package/ts_build/src/agents/tools/ast/astListPaths.d.ts +1 -1
- package/ts_build/src/agents/tools/ast/astListPaths.js +2 -72
- package/ts_build/src/agents/tools/ast/astListPaths.js.map +1 -1
- package/ts_build/src/agents/tools/executeScript/index.d.ts +3 -2
- package/ts_build/src/agents/tools/executeScript/index.js +4 -1
- package/ts_build/src/agents/tools/executeScript/index.js.map +1 -1
- package/ts_build/src/agents/tools/googleSearch.js +2 -2
- package/ts_build/src/agents/tools/googleSearch.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +0 -3
- package/ts_build/src/agents/tools/index.js +0 -3
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +0 -138
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/loadWebpage.js +1 -89
- package/ts_build/src/agents/tools/loadWebpage.js.map +1 -1
- package/ts_build/src/agents/tools/textSearch.d.ts +1 -1
- package/ts_build/src/auth/browserLogin.js +7 -7
- package/ts_build/src/auth/browserLogin.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/types.d.ts +1 -1
- package/ts_build/src/cli.d.ts +1 -1
- package/ts_build/src/cli.js +47 -1
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/gemini.d.ts +1 -73
- package/ts_build/src/clients/gemini.js +57 -19
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/http.js +5 -9
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/pricing/google.d.ts +17 -73
- package/ts_build/src/clients/pricing/google.js +47 -10
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/conversion.d.ts +1 -4
- package/ts_build/src/conversion.js +12 -27
- package/ts_build/src/conversion.js.map +1 -1
- package/ts_build/src/index.d.ts +4 -0
- package/ts_build/src/index.js +15 -14
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/login.js +5 -4
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/plugins/downloader/downloader.js +3 -3
- package/ts_build/src/plugins/downloader/downloader.js.map +1 -1
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/plugins/plugins.js +0 -14
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/tree-sitter/editor.d.ts +3 -32
- package/ts_build/src/plugins/tree-sitter/editor.js +6 -208
- package/ts_build/src/plugins/tree-sitter/editor.js.map +1 -1
- package/ts_build/src/plugins/tree-sitter/parser.d.ts +19 -54
- package/ts_build/src/plugins/tree-sitter/parser.js +19 -293
- package/ts_build/src/plugins/tree-sitter/parser.js.map +1 -1
- package/ts_build/src/plugins/tree-sitter/simple-paths.d.ts +2 -15
- package/ts_build/src/plugins/tree-sitter/simple-paths.js +2 -324
- package/ts_build/src/plugins/tree-sitter/simple-paths.js.map +1 -1
- package/ts_build/src/plugins/url.js +27 -8
- package/ts_build/src/plugins/url.js.map +1 -1
- package/ts_build/src/services/EmbeddingsService.d.ts +14 -0
- package/ts_build/src/services/EmbeddingsService.js +33 -0
- package/ts_build/src/services/EmbeddingsService.js.map +1 -0
- package/ts_build/src/services/GitHub.js +2 -2
- package/ts_build/src/services/GitHub.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +29 -29
- package/ts_build/src/services/KnowhowClient.js +33 -33
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/MediaProcessorService.d.ts +22 -0
- package/ts_build/src/services/MediaProcessorService.js +215 -0
- package/ts_build/src/services/MediaProcessorService.js.map +1 -0
- package/ts_build/src/services/S3.d.ts +0 -4
- package/ts_build/src/services/S3.js +14 -60
- package/ts_build/src/services/S3.js.map +1 -1
- package/ts_build/src/services/index.d.ts +6 -5
- package/ts_build/src/services/index.js +6 -6
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/index.js +12 -3
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +8 -2
- package/ts_build/src/services/script-execution/ScriptExecutor.js +22 -7
- package/ts_build/src/services/script-execution/ScriptExecutor.js.map +1 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +1 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.js +4 -2
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +1 -1
- package/ts_build/src/types.d.ts +1 -0
- package/ts_build/src/types.js +1 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/utils/http.d.ts +27 -0
- package/ts_build/src/utils/http.js +98 -0
- package/ts_build/src/utils/http.js.map +1 -0
- package/ts_build/src/workers/auth/PasskeySetup.js +6 -7
- package/ts_build/src/workers/auth/PasskeySetup.js.map +1 -1
- package/ts_build/tests/clients/AIClient.test.js +11 -14
- package/ts_build/tests/clients/AIClient.test.js.map +1 -1
- package/ts_build/tests/manual/file-edits/figma.test.d.ts +0 -1
- package/ts_build/tests/manual/file-edits/figma.test.js +1 -46
- package/ts_build/tests/manual/file-edits/figma.test.js.map +1 -1
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js +2 -0
- package/ts_build/tests/plugins/language/languagePlugin-content-triggers.test.js.map +1 -1
- package/ts_build/tests/plugins/language/languagePlugin.test.js +2 -0
- package/ts_build/tests/plugins/language/languagePlugin.test.js.map +1 -1
- package/ts_build/tests/processors/ToolResponseCache.test.js +2 -2
- package/ts_build/tests/processors/ToolResponseCache.test.js.map +1 -1
- package/ts_build/tests/test.spec.js +0 -14
- package/ts_build/tests/test.spec.js.map +1 -1
- package/ts_build/tests/tree-sitter/tree-sitter.test.d.ts +0 -1
- package/ts_build/tests/tree-sitter/tree-sitter.test.js +2 -183
- package/ts_build/tests/tree-sitter/tree-sitter.test.js.map +1 -1
- package/ts_build/tests/unit/modules/moduleLoading.test.js +11 -4
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +4 -4
- package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
- package/benchmarks/.dockerignore +0 -7
- package/benchmarks/README.md +0 -166
- package/benchmarks/docker/Dockerfile +0 -68
- package/benchmarks/example-config.yml +0 -27
- package/benchmarks/jest.config.js +0 -13
- package/benchmarks/package-lock.json +0 -4297
- package/benchmarks/package.json +0 -39
- package/benchmarks/results/27b0a06/2025-09-27/xai/xai-grok-code-fast-1.json +0 -2909
- package/benchmarks/results/4057aed/2025-08-14/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -1671
- package/benchmarks/results/4542435/2025-08-05/lms/lms-openai-gpt-oss-20b.json +0 -2814
- package/benchmarks/results/4542435/2025-08-05/lms/lms-qwen-qwen3-30b-a3b-2507.json +0 -2014
- package/benchmarks/results/4fb9125/2025-08-07/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3121
- package/benchmarks/results/5766aee/2025-08-02/lms-qwen/qwen3-coder-30b.json +0 -98
- package/benchmarks/results/6d73808/2025-08-07/openai/openai-gpt-5.json +0 -3256
- package/benchmarks/results/77bf0a6/2025-08-02/lms-qwen/qwen3-30b-a3b-2507.json +0 -4298
- package/benchmarks/results/8c0d445/2025-08-03/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3031
- package/benchmarks/results/8c0d445/2025-08-03/openai/openai-gpt-4.1-2025-04-14.json +0 -2990
- package/benchmarks/results/ac6b2ab/2025-08-03/anthropic/anthropic-claude-sonnet-4-20250514.json +0 -3256
- package/benchmarks/results/ac6b2ab/2025-08-03/lms/lms-qwen-qwen3-coder-30b.json +0 -3007
- package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-2025-04-14.json +0 -3256
- package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-mini-2025-04-14.json +0 -3036
- package/benchmarks/results/ac6b2ab/2025-08-03/openai/openai-gpt-4.1-nano-2025-04-14.json +0 -3280
- package/benchmarks/results/adff675/2025-08-04/lms/lms-qwen-qwen3-30b-a3b-2507.json +0 -1920
- package/benchmarks/results/adff675/2025-08-04/lms/lms-qwen-qwen3-coder-30b.json +0 -3281
- package/benchmarks/results/b502ed9/2025-08-03/lms-qwen/qwen3-coder-30b.json +0 -2896
- package/benchmarks/results/d1a8129/2025-08-03/lms/lms-qwen-qwen3-coder-30b.json +0 -3011
- package/benchmarks/results/e60471c/2025-08-03/lms/qwen3-30b-a3b-2507.json +0 -3003
- package/benchmarks/scripts/build-and-run.sh +0 -47
- package/benchmarks/scripts/clone-exercism.sh +0 -92
- package/benchmarks/scripts/validate.sh +0 -48
- package/benchmarks/src/__tests__/runner.test.ts +0 -27
- package/benchmarks/src/cli.ts +0 -90
- package/benchmarks/src/evaluators/EvaluatorRegistry.ts +0 -64
- package/benchmarks/src/evaluators/JavaScriptEvaluator.ts +0 -183
- package/benchmarks/src/evaluators/index.ts +0 -3
- package/benchmarks/src/evaluators/types.ts +0 -22
- package/benchmarks/src/index.ts +0 -3
- package/benchmarks/src/providers.ts +0 -13
- package/benchmarks/src/runner.ts +0 -824
- package/benchmarks/src/types.ts +0 -63
- package/benchmarks/tsconfig.json +0 -19
- package/leaderboard/README.md +0 -148
- package/leaderboard/app/api/benchmark-data/route.ts +0 -131
- package/leaderboard/app/api/benchmark-detail/route.ts +0 -172
- package/leaderboard/app/details/[model]/[provider]/[language]/page.tsx +0 -501
- package/leaderboard/app/exercise/[model]/[provider]/[language]/[exercise]/page.tsx +0 -375
- package/leaderboard/app/globals.css +0 -27
- package/leaderboard/app/layout.tsx +0 -21
- package/leaderboard/app/page.tsx +0 -170
- package/leaderboard/components/LeaderboardTable.tsx +0 -168
- package/leaderboard/components/PerformanceChart.tsx +0 -109
- package/leaderboard/next-env.d.ts +0 -5
- package/leaderboard/next.config.js +0 -4
- package/leaderboard/package-lock.json +0 -6363
- package/leaderboard/package.json +0 -28
- package/leaderboard/postcss.config.js +0 -6
- package/leaderboard/tailwind.config.js +0 -17
- package/leaderboard/tsconfig.json +0 -28
- package/leaderboard/types/benchmark.ts +0 -67
- package/leaderboard/utils/dataProcessor.ts +0 -33
- package/src/agents/tools/asana/definitions.ts +0 -199
- package/src/agents/tools/asana/index.ts +0 -108
- package/src/agents/tools/ast/astAppendNode.ts +0 -90
- package/src/agents/tools/ast/astDeleteNode.ts +0 -88
- package/src/agents/tools/ast/astEditNode.ts +0 -95
- package/src/agents/tools/ast/astGetPathForLine.ts +0 -73
- package/src/agents/tools/ast/astListPaths.ts +0 -66
- package/src/agents/tools/ast/index.ts +0 -7
- package/src/agents/tools/github/definitions.ts +0 -89
- package/src/agents/tools/github/index.ts +0 -67
- package/src/chat-old.ts +0 -446
- package/src/plugins/asana.ts +0 -146
- package/src/plugins/downloader/plugin.ts +0 -103
- package/src/plugins/downloader/types.ts +0 -92
- package/src/plugins/figma.ts +0 -158
- package/src/plugins/github.ts +0 -219
- package/src/plugins/jira.ts +0 -115
- package/src/plugins/linear.ts +0 -230
- package/src/plugins/notion.ts +0 -179
- package/src/plugins/tree-sitter/editor.ts +0 -369
- package/src/plugins/tree-sitter/lang-packs/index.ts +0 -23
- package/src/plugins/tree-sitter/lang-packs/java.ts +0 -59
- package/src/plugins/tree-sitter/lang-packs/javascript.ts +0 -57
- package/src/plugins/tree-sitter/lang-packs/python.ts +0 -45
- package/src/plugins/tree-sitter/lang-packs/types.ts +0 -79
- package/src/plugins/tree-sitter/lang-packs/typescript.ts +0 -49
- package/src/plugins/tree-sitter/parser.ts +0 -470
- package/src/plugins/tree-sitter/simple-paths.ts +0 -467
- package/src/services/GitHub.ts +0 -59
- package/tests/tree-sitter/editor.test.ts +0 -113
- package/tests/tree-sitter/invalid.test.ts +0 -299
- package/tests/tree-sitter/paths/common-edits.test.ts +0 -564
- package/tests/tree-sitter/paths/debug-exact-position.test.ts +0 -44
- package/tests/tree-sitter/paths/debug-line-indexing.test.ts +0 -49
- package/tests/tree-sitter/paths/debug-paths.test.ts +0 -90
- package/tests/tree-sitter/paths/paths.test.ts +0 -170
- package/tests/tree-sitter/paths/simple-paths.test.ts +0 -367
- package/tests/tree-sitter/sample-after.ts +0 -48
- package/tests/tree-sitter/sample-before.ts +0 -25
- package/tests/tree-sitter/test-files/completely-broken.ts +0 -7
- package/tests/tree-sitter/test-files/duplicate-braces.ts +0 -39
- package/tests/tree-sitter/test-files/invalid-nesting.ts +0 -39
- package/tests/tree-sitter/test-files/malformed-signature.ts +0 -39
- package/tests/tree-sitter/test-files/mismatched-parens.ts +0 -39
- package/tests/tree-sitter/test-files/missing-semicolon.ts +0 -39
- package/tests/tree-sitter/test-files/partially-broken.ts +0 -20
- package/tests/tree-sitter/test-files/specific-errors.ts +0 -14
- package/tests/tree-sitter/test-files/unclosed-string.ts +0 -39
- package/tests/tree-sitter/tree-sitter.test.ts +0 -251
|
@@ -1,82 +1,54 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { execAsync, fileExists, readFile, mkdir } from "../../utils";
|
|
8
|
-
import { Clients } from "../../clients";
|
|
9
|
-
import { Models } from "../../types";
|
|
10
|
-
|
|
11
|
-
const logger = Logger();
|
|
12
|
-
|
|
13
|
-
export class DownloaderService {
|
|
14
|
-
constructor(private clients: typeof Clients) {}
|
|
15
|
-
|
|
16
|
-
async askGptVision(
|
|
17
|
-
imageUrl: string,
|
|
18
|
-
question: string,
|
|
19
|
-
provider = "openai",
|
|
20
|
-
model = Models.openai.GPT_4o
|
|
21
|
-
) {
|
|
22
|
-
const response = await this.clients.createCompletion(provider, {
|
|
23
|
-
model,
|
|
24
|
-
max_tokens: 2500,
|
|
25
|
-
messages: [
|
|
26
|
-
{
|
|
27
|
-
role: "user",
|
|
28
|
-
content: [
|
|
29
|
-
{ type: "text", text: question },
|
|
30
|
-
{
|
|
31
|
-
type: "image_url",
|
|
32
|
-
image_url: {
|
|
33
|
-
url: imageUrl,
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
});
|
|
3
|
+
import { exec } from "child_process";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
import { fileExists, readFile, mkdir } from "../utils";
|
|
6
|
+
import { AIClient } from "../clients";
|
|
40
7
|
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async download(url: string, outputDir: string) {
|
|
45
|
-
const info = await this.info(url);
|
|
46
|
-
const exists = await fileExists(`${outputDir}/${info.id}.${info.ext}`);
|
|
8
|
+
const execPromise = promisify(exec);
|
|
47
9
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
10
|
+
async function execAsync(command: string): Promise<string> {
|
|
11
|
+
const { stdout, stderr } = await execPromise(command);
|
|
12
|
+
return stdout + stderr;
|
|
13
|
+
}
|
|
52
14
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
15
|
+
export interface TranscriptChunk {
|
|
16
|
+
chunkPath: string;
|
|
17
|
+
text: string;
|
|
18
|
+
usd_cost: number;
|
|
19
|
+
}
|
|
57
20
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return info;
|
|
65
|
-
}
|
|
21
|
+
export interface KeyframeInfo {
|
|
22
|
+
path: string;
|
|
23
|
+
description: string;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
usd_cost?: number;
|
|
26
|
+
}
|
|
66
27
|
|
|
28
|
+
/**
|
|
29
|
+
* MediaProcessorService handles audio/video processing using:
|
|
30
|
+
* - ffmpeg (system tool) for chunking audio/video
|
|
31
|
+
* - OpenAI Whisper API for transcription
|
|
32
|
+
*
|
|
33
|
+
* This is part of the core services because microphone recording and
|
|
34
|
+
* audio-to-text transcription are base CLI features. The DownloaderService
|
|
35
|
+
* (in @tyvm/knowhow-module-video-downloader) uses this service for the
|
|
36
|
+
* audio/video processing steps after downloading with ytdl.
|
|
37
|
+
*/
|
|
38
|
+
export class MediaProcessorService {
|
|
39
|
+
constructor(private clients: any) {}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Split an audio/video file into fixed-length mp3 chunks using ffmpeg.
|
|
43
|
+
*/
|
|
67
44
|
public async chunk(
|
|
68
45
|
filePath: string,
|
|
69
46
|
outputDir: string,
|
|
70
47
|
CHUNK_LENGTH_SECONDS = 30,
|
|
71
48
|
reuseExistingChunks = true
|
|
72
|
-
) {
|
|
49
|
+
): Promise<string[]> {
|
|
73
50
|
const parsed = path.parse(filePath);
|
|
74
51
|
const fileName = parsed.name;
|
|
75
|
-
const fileExt = parsed.ext;
|
|
76
|
-
console.log({ fileName, fileExt });
|
|
77
|
-
console.log("Chunking file", filePath);
|
|
78
|
-
|
|
79
|
-
// create a temp directory
|
|
80
52
|
const outputDirPath = path.join(outputDir, `${fileName}/chunks`);
|
|
81
53
|
await fs.promises.mkdir(outputDirPath, { recursive: true });
|
|
82
54
|
const doneFilePath = path.join(outputDirPath, ".chunking_done");
|
|
@@ -90,10 +62,9 @@ export class DownloaderService {
|
|
|
90
62
|
if (existingChunkNames.length > 0 && doneFileExists) {
|
|
91
63
|
if (reuseExistingChunks) {
|
|
92
64
|
console.log("Chunks already exist, skipping");
|
|
93
|
-
|
|
65
|
+
return existingChunkNames.map((chunkName) =>
|
|
94
66
|
path.join(outputDirPath, chunkName)
|
|
95
67
|
);
|
|
96
|
-
return names;
|
|
97
68
|
} else {
|
|
98
69
|
for (const file of existingFolderFiles) {
|
|
99
70
|
fs.rmSync(path.join(outputDirPath, file), { recursive: true });
|
|
@@ -113,6 +84,9 @@ export class DownloaderService {
|
|
|
113
84
|
return chunkNames.map((chunkName) => path.join(outputDirPath, chunkName));
|
|
114
85
|
}
|
|
115
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Stream transcription of audio chunks using Whisper.
|
|
89
|
+
*/
|
|
116
90
|
public async *streamTranscription(
|
|
117
91
|
files: string[],
|
|
118
92
|
outputPath: string,
|
|
@@ -123,13 +97,11 @@ export class DownloaderService {
|
|
|
123
97
|
console.log("Transcription already exists, using cached data");
|
|
124
98
|
const contents = await readFile(outputPath);
|
|
125
99
|
const data = JSON.parse(contents.toString()) as TranscriptChunk[];
|
|
126
|
-
for (const item of data)
|
|
127
|
-
yield item;
|
|
128
|
-
}
|
|
100
|
+
for (const item of data) yield item;
|
|
129
101
|
return;
|
|
130
102
|
}
|
|
131
103
|
|
|
132
|
-
const allTranscripts = [];
|
|
104
|
+
const allTranscripts: TranscriptChunk[] = [];
|
|
133
105
|
for (const file of files) {
|
|
134
106
|
const chunkName = path.parse(file).name;
|
|
135
107
|
const chunkTranscriptPath = path.join(
|
|
@@ -139,17 +111,12 @@ export class DownloaderService {
|
|
|
139
111
|
const chunkExists = await fileExists(chunkTranscriptPath);
|
|
140
112
|
|
|
141
113
|
if (chunkExists && reusePreviousTranscript) {
|
|
142
|
-
console.log(
|
|
143
|
-
chunkTranscriptPath,
|
|
144
|
-
" transcription already exists, using cached data"
|
|
145
|
-
);
|
|
146
114
|
const contents = await readFile(chunkTranscriptPath);
|
|
147
|
-
const cached = {
|
|
115
|
+
const cached: TranscriptChunk = {
|
|
148
116
|
chunkPath: chunkTranscriptPath,
|
|
149
117
|
text: contents.toString(),
|
|
150
118
|
usd_cost: 0,
|
|
151
119
|
};
|
|
152
|
-
|
|
153
120
|
yield cached;
|
|
154
121
|
allTranscripts.push(cached);
|
|
155
122
|
continue;
|
|
@@ -163,7 +130,7 @@ export class DownloaderService {
|
|
|
163
130
|
fileName: path.basename(file),
|
|
164
131
|
model: "whisper-1",
|
|
165
132
|
})
|
|
166
|
-
.catch((e) => {
|
|
133
|
+
.catch((e: any) => {
|
|
167
134
|
console.error("Error transcribing", file, e);
|
|
168
135
|
return { text: "" };
|
|
169
136
|
});
|
|
@@ -171,11 +138,10 @@ export class DownloaderService {
|
|
|
171
138
|
await mkdir(path.dirname(chunkTranscriptPath), { recursive: true });
|
|
172
139
|
await fs.promises.writeFile(chunkTranscriptPath, transcript.text);
|
|
173
140
|
|
|
174
|
-
|
|
175
|
-
const data = {
|
|
141
|
+
const data: TranscriptChunk = {
|
|
176
142
|
chunkPath: chunkTranscriptPath,
|
|
177
143
|
text: transcript.text,
|
|
178
|
-
usd_cost: 30 * 0.0001,
|
|
144
|
+
usd_cost: 30 * 0.0001,
|
|
179
145
|
};
|
|
180
146
|
yield data;
|
|
181
147
|
allTranscripts.push(data);
|
|
@@ -184,6 +150,9 @@ export class DownloaderService {
|
|
|
184
150
|
fs.writeFileSync(outputPath, JSON.stringify(allTranscripts, null, 2));
|
|
185
151
|
}
|
|
186
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Transcribe all audio chunks and return the text strings.
|
|
155
|
+
*/
|
|
187
156
|
public async transcribeChunks(
|
|
188
157
|
files: string[],
|
|
189
158
|
outputPath: string,
|
|
@@ -191,18 +160,16 @@ export class DownloaderService {
|
|
|
191
160
|
): Promise<string[]> {
|
|
192
161
|
const exists = await fileExists(outputPath);
|
|
193
162
|
if (exists && reusePreviousTranscript) {
|
|
194
|
-
console.log("Transcription already exists, using cached data");
|
|
195
163
|
const contents = await readFile(outputPath);
|
|
196
164
|
return JSON.parse(contents.toString()) as string[];
|
|
197
165
|
}
|
|
198
166
|
|
|
199
|
-
const fullText = [];
|
|
200
|
-
for await (const {
|
|
167
|
+
const fullText: string[] = [];
|
|
168
|
+
for await (const { text } of this.streamTranscription(
|
|
201
169
|
files,
|
|
202
170
|
outputPath,
|
|
203
171
|
reusePreviousTranscript
|
|
204
172
|
)) {
|
|
205
|
-
console.log("Chunk transcribed:", chunkPath);
|
|
206
173
|
fullText.push(text);
|
|
207
174
|
}
|
|
208
175
|
|
|
@@ -210,42 +177,65 @@ export class DownloaderService {
|
|
|
210
177
|
return fullText;
|
|
211
178
|
}
|
|
212
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Process an audio/video file: chunk it with ffmpeg, then transcribe each chunk.
|
|
182
|
+
* Returns an array of transcript strings (one per chunk).
|
|
183
|
+
*/
|
|
184
|
+
public async processAudio(
|
|
185
|
+
filePath: string,
|
|
186
|
+
reusePreviousTranscript = true,
|
|
187
|
+
chunkTime = 30
|
|
188
|
+
): Promise<string[]> {
|
|
189
|
+
const parsed = path.parse(filePath);
|
|
190
|
+
const outputPath = `${parsed.dir}/${parsed.name}/transcript.json`;
|
|
191
|
+
|
|
192
|
+
const exists = await fileExists(outputPath);
|
|
193
|
+
if (exists && reusePreviousTranscript) {
|
|
194
|
+
const fileContent = (await readFile(outputPath, "utf8")) as string;
|
|
195
|
+
return outputPath.endsWith("txt")
|
|
196
|
+
? fileContent.split("\n")
|
|
197
|
+
: JSON.parse(fileContent);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const chunks = await this.chunk(
|
|
201
|
+
filePath,
|
|
202
|
+
parsed.dir,
|
|
203
|
+
chunkTime,
|
|
204
|
+
reusePreviousTranscript
|
|
205
|
+
);
|
|
206
|
+
return this.transcribeChunks(chunks, outputPath, reusePreviousTranscript);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Extract keyframes from a video file using ffmpeg, then describe each with vision AI.
|
|
211
|
+
*/
|
|
213
212
|
public async *streamKeyFrameExtraction(
|
|
214
213
|
filePath: string,
|
|
215
214
|
videoJsonPath: string,
|
|
216
|
-
reusePreviousKeyframes
|
|
217
|
-
interval
|
|
215
|
+
reusePreviousKeyframes = true,
|
|
216
|
+
interval = 10
|
|
218
217
|
): AsyncGenerator<KeyframeInfo> {
|
|
219
218
|
if (reusePreviousKeyframes && fs.existsSync(videoJsonPath)) {
|
|
220
|
-
console.log("Keyframes already exist, using cached data");
|
|
221
219
|
const contents = await readFile(videoJsonPath);
|
|
222
220
|
const data = JSON.parse(contents.toString()) as KeyframeInfo[];
|
|
223
|
-
for (const keyframe of data) {
|
|
224
|
-
yield { ...keyframe, usd_cost: 0 };
|
|
225
|
-
}
|
|
221
|
+
for (const keyframe of data) yield { ...keyframe, usd_cost: 0 };
|
|
226
222
|
return;
|
|
227
223
|
}
|
|
228
224
|
|
|
229
|
-
const parsed = path.parse(filePath);
|
|
230
225
|
const outputDir = path.dirname(videoJsonPath);
|
|
231
|
-
const
|
|
232
|
-
const keyframesDir = path.join(outputDir, `/keyframes`);
|
|
226
|
+
const keyframesDir = path.join(outputDir, "keyframes");
|
|
233
227
|
await fs.promises.mkdir(keyframesDir, { recursive: true });
|
|
234
228
|
|
|
235
229
|
const command = `ffmpeg -i "${filePath}" -vf "fps=1/${interval},scale=640:-1" "${keyframesDir}/frame%04d.jpg"`;
|
|
236
230
|
await execAsync(command);
|
|
237
|
-
console.log("Extracting keyframe:", command);
|
|
238
231
|
|
|
239
232
|
const keyframes = await fs.promises.readdir(keyframesDir);
|
|
233
|
+
const allKeyframes: KeyframeInfo[] = [];
|
|
240
234
|
|
|
241
|
-
const allKeyframes = [];
|
|
242
235
|
for (const keyframe of keyframes) {
|
|
243
236
|
const keyframePath = path.join(keyframesDir, keyframe);
|
|
244
237
|
const keyframeName = path.parse(keyframe).name;
|
|
245
|
-
const keyframeDescriptionPath = path.join(
|
|
246
|
-
keyframesDir,
|
|
247
|
-
`${keyframeName}.json`
|
|
248
|
-
);
|
|
238
|
+
const keyframeDescriptionPath = path.join(keyframesDir, `${keyframeName}.json`);
|
|
249
239
|
const descriptionExists = await fileExists(keyframeDescriptionPath);
|
|
250
240
|
|
|
251
241
|
if (descriptionExists && reusePreviousKeyframes) {
|
|
@@ -257,10 +247,11 @@ export class DownloaderService {
|
|
|
257
247
|
}
|
|
258
248
|
|
|
259
249
|
const description = await this.describeKeyframe(keyframePath);
|
|
260
|
-
const
|
|
250
|
+
const frameNumber = parseInt(keyframe.match(/\d+/)?.[0] ?? "0", 10);
|
|
251
|
+
const keyframeJson: KeyframeInfo = {
|
|
261
252
|
path: keyframePath,
|
|
262
253
|
description: description.choices[0].message.content,
|
|
263
|
-
timestamp:
|
|
254
|
+
timestamp: frameNumber * interval,
|
|
264
255
|
usd_cost: description.usd_cost,
|
|
265
256
|
};
|
|
266
257
|
await fs.promises.writeFile(
|
|
@@ -271,17 +262,14 @@ export class DownloaderService {
|
|
|
271
262
|
allKeyframes.push(keyframeJson);
|
|
272
263
|
}
|
|
273
264
|
|
|
274
|
-
await fs.promises.writeFile(
|
|
275
|
-
videoJsonPath,
|
|
276
|
-
JSON.stringify(allKeyframes, null, 2)
|
|
277
|
-
);
|
|
265
|
+
await fs.promises.writeFile(videoJsonPath, JSON.stringify(allKeyframes, null, 2));
|
|
278
266
|
}
|
|
279
267
|
|
|
280
268
|
public async extractKeyframes(
|
|
281
269
|
filePath: string,
|
|
282
270
|
outputPath: string,
|
|
283
|
-
reusePreviousKeyframes
|
|
284
|
-
interval
|
|
271
|
+
reusePreviousKeyframes = true,
|
|
272
|
+
interval = 10
|
|
285
273
|
): Promise<KeyframeInfo[]> {
|
|
286
274
|
const keyframes: KeyframeInfo[] = [];
|
|
287
275
|
for await (const keyframe of this.streamKeyFrameExtraction(
|
|
@@ -292,7 +280,6 @@ export class DownloaderService {
|
|
|
292
280
|
)) {
|
|
293
281
|
keyframes.push(keyframe);
|
|
294
282
|
}
|
|
295
|
-
|
|
296
283
|
await fs.promises.writeFile(outputPath, JSON.stringify(keyframes, null, 2));
|
|
297
284
|
return keyframes;
|
|
298
285
|
}
|
|
@@ -300,165 +287,20 @@ export class DownloaderService {
|
|
|
300
287
|
private async describeKeyframe(keyframePath: string) {
|
|
301
288
|
const question =
|
|
302
289
|
"Describe this image in detail, focusing on the main elements and actions visible.";
|
|
303
|
-
const base64 = await fs.promises.readFile(keyframePath, {
|
|
304
|
-
encoding: "base64",
|
|
305
|
-
});
|
|
290
|
+
const base64 = await fs.promises.readFile(keyframePath, { encoding: "base64" });
|
|
306
291
|
const image = `data:image/jpeg;base64,${base64}`;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
async processAudio(
|
|
318
|
-
filePath: string,
|
|
319
|
-
reusePreviousTranscript = true,
|
|
320
|
-
chunkTime = 30
|
|
321
|
-
): Promise<string[]> {
|
|
322
|
-
const parsed = path.parse(filePath);
|
|
323
|
-
const outputPath = `${parsed.dir}/${parsed.name}/transcript.json`;
|
|
324
|
-
|
|
325
|
-
// Skip chunking if the full output exists
|
|
326
|
-
const exists = await fileExists(outputPath);
|
|
327
|
-
if (exists && reusePreviousTranscript) {
|
|
328
|
-
console.log(
|
|
329
|
-
`Transcription ${outputPath} already exists, using cached data`
|
|
330
|
-
);
|
|
331
|
-
const fileContent = await readFile(outputPath, "utf8");
|
|
332
|
-
return outputPath.endsWith("txt")
|
|
333
|
-
? fileContent.split("\n")
|
|
334
|
-
: JSON.parse(fileContent);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const chunks = await this.chunk(
|
|
338
|
-
filePath,
|
|
339
|
-
parsed.dir,
|
|
340
|
-
chunkTime,
|
|
341
|
-
reusePreviousTranscript
|
|
342
|
-
);
|
|
343
|
-
const transcription = await this.transcribeChunks(
|
|
344
|
-
chunks,
|
|
345
|
-
outputPath,
|
|
346
|
-
reusePreviousTranscript
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
return transcription;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async *streamProcessAudio(
|
|
353
|
-
filePath: string,
|
|
354
|
-
reusePreviousTranscript = true,
|
|
355
|
-
chunkTime = 30
|
|
356
|
-
): AsyncGenerator<TranscriptChunk> {
|
|
357
|
-
const parsed = path.parse(filePath);
|
|
358
|
-
const outputPath = `${parsed.dir}/${parsed.name}/transcript.json`;
|
|
359
|
-
|
|
360
|
-
// Skip chunking if the full output exists
|
|
361
|
-
const exists = await fileExists(outputPath);
|
|
362
|
-
if (exists && reusePreviousTranscript) {
|
|
363
|
-
console.log(
|
|
364
|
-
`Transcription ${outputPath} already exists, using cached data`
|
|
365
|
-
);
|
|
366
|
-
const fileContent = await readFile(outputPath, "utf8");
|
|
367
|
-
const lines = outputPath.endsWith("txt")
|
|
368
|
-
? fileContent.split("\n")
|
|
369
|
-
: JSON.parse(fileContent);
|
|
370
|
-
|
|
371
|
-
for (const line of lines) {
|
|
372
|
-
if (typeof line === "string") {
|
|
373
|
-
yield { chunkPath: "", text: line, usd_cost: 0 };
|
|
374
|
-
} else {
|
|
375
|
-
yield line as TranscriptChunk;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const chunks = await this.chunk(
|
|
382
|
-
filePath,
|
|
383
|
-
parsed.dir,
|
|
384
|
-
chunkTime,
|
|
385
|
-
reusePreviousTranscript
|
|
386
|
-
);
|
|
387
|
-
|
|
388
|
-
for await (const chunk of this.streamTranscription(
|
|
389
|
-
chunks,
|
|
390
|
-
outputPath,
|
|
391
|
-
reusePreviousTranscript
|
|
392
|
-
)) {
|
|
393
|
-
yield chunk;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
async processVideo(
|
|
398
|
-
filePath: string,
|
|
399
|
-
reusePreviousTranscript = true,
|
|
400
|
-
chunkTime = 30
|
|
401
|
-
) {
|
|
402
|
-
const parsed = path.parse(filePath);
|
|
403
|
-
const outputPath = `${parsed.dir}/${parsed.name}/video.json`;
|
|
404
|
-
|
|
405
|
-
console.log("Processing audio...");
|
|
406
|
-
const transcriptions = await this.processAudio(
|
|
407
|
-
filePath,
|
|
408
|
-
reusePreviousTranscript,
|
|
409
|
-
chunkTime
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
console.log("Extracting keyframes...");
|
|
413
|
-
const videoAnalysis = await this.extractKeyframes(
|
|
414
|
-
filePath,
|
|
415
|
-
outputPath,
|
|
416
|
-
reusePreviousTranscript,
|
|
417
|
-
chunkTime
|
|
418
|
-
);
|
|
419
|
-
|
|
420
|
-
return videoAnalysis.map((frame, index) => {
|
|
421
|
-
return {
|
|
422
|
-
frame,
|
|
423
|
-
transcription: transcriptions[index],
|
|
424
|
-
};
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
async *streamProcessVideo(
|
|
429
|
-
filePath: string,
|
|
430
|
-
reusePreviousTranscript = true,
|
|
431
|
-
chunkTime = 30
|
|
432
|
-
) {
|
|
433
|
-
const parsed = path.parse(filePath);
|
|
434
|
-
const videoJson = `${parsed.dir}/${parsed.name}/video.json`;
|
|
435
|
-
|
|
436
|
-
console.log("Processing audio...");
|
|
437
|
-
const transcriptions = this.streamProcessAudio(
|
|
438
|
-
filePath,
|
|
439
|
-
reusePreviousTranscript,
|
|
440
|
-
chunkTime
|
|
441
|
-
);
|
|
442
|
-
|
|
443
|
-
console.log("Extracting keyframes...");
|
|
444
|
-
const videoAnalysis = this.streamKeyFrameExtraction(
|
|
445
|
-
filePath,
|
|
446
|
-
videoJson,
|
|
447
|
-
reusePreviousTranscript,
|
|
448
|
-
chunkTime
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
for await (const frame of videoAnalysis) {
|
|
452
|
-
const transcription = (await transcriptions.next())
|
|
453
|
-
?.value as TranscriptChunk;
|
|
454
|
-
yield {
|
|
455
|
-
frame,
|
|
456
|
-
transcription: transcription || {
|
|
457
|
-
chunkPath: "",
|
|
458
|
-
text: "[missing transcript]",
|
|
459
|
-
usd_cost: 0,
|
|
292
|
+
return this.clients.createCompletion("openai", {
|
|
293
|
+
model: "gpt-4o",
|
|
294
|
+
max_tokens: 2500,
|
|
295
|
+
messages: [
|
|
296
|
+
{
|
|
297
|
+
role: "user",
|
|
298
|
+
content: [
|
|
299
|
+
{ type: "text", text: question },
|
|
300
|
+
{ type: "image_url", image_url: { url: image } },
|
|
301
|
+
],
|
|
460
302
|
},
|
|
461
|
-
|
|
462
|
-
}
|
|
303
|
+
],
|
|
304
|
+
});
|
|
463
305
|
}
|
|
464
306
|
}
|
package/src/services/S3.ts
CHANGED
|
@@ -1,101 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
S3Client,
|
|
3
|
-
HeadBucketCommand,
|
|
4
|
-
CreateBucketCommand,
|
|
5
|
-
GetObjectCommand,
|
|
6
|
-
PutObjectCommand,
|
|
7
|
-
GetObjectCommandInput,
|
|
8
|
-
PutObjectCommandInput,
|
|
9
|
-
} from "@aws-sdk/client-s3";
|
|
10
|
-
|
|
11
1
|
import * as fs from "fs";
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
import * as path from "path";
|
|
15
|
-
import { pipeline } from "stream";
|
|
2
|
+
import { createWriteStream, createReadStream } from "fs";
|
|
3
|
+
import { pipeline, Readable } from "stream";
|
|
16
4
|
import * as util from "util";
|
|
17
|
-
import axios from "axios";
|
|
18
5
|
|
|
19
|
-
import { createReadStream } from "fs";
|
|
20
6
|
const pipelineAsync = util.promisify(pipeline);
|
|
21
7
|
|
|
22
8
|
export class S3Service {
|
|
23
|
-
private s3: S3Client;
|
|
24
|
-
|
|
25
|
-
constructor() {
|
|
26
|
-
this.s3 = new S3Client();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async uploadFile(
|
|
30
|
-
filePath: string,
|
|
31
|
-
bucketName: string,
|
|
32
|
-
key: string
|
|
33
|
-
): Promise<void> {
|
|
34
|
-
const fileContent = await fsPromises.readFile(filePath);
|
|
35
|
-
|
|
36
|
-
const params = {
|
|
37
|
-
Bucket: bucketName,
|
|
38
|
-
Key: key,
|
|
39
|
-
Body: fileContent,
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// create bucket if it doesn't exist
|
|
43
|
-
try {
|
|
44
|
-
await this.s3.send(new HeadBucketCommand({ Bucket: bucketName }));
|
|
45
|
-
} catch (error) {
|
|
46
|
-
const statusCode = error.$metadata.httpStatusCode;
|
|
47
|
-
if (statusCode === 404) {
|
|
48
|
-
await this.s3.send(new CreateBucketCommand({ Bucket: bucketName }));
|
|
49
|
-
console.log(`Bucket ${bucketName} created successfully`);
|
|
50
|
-
} else {
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
await this.s3.send(new PutObjectCommand(params));
|
|
56
|
-
console.log(`File uploaded successfully to ${bucketName}/${key}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async downloadFile(
|
|
60
|
-
bucketName: string,
|
|
61
|
-
key: string,
|
|
62
|
-
destinationPath: string
|
|
63
|
-
): Promise<void> {
|
|
64
|
-
const params = {
|
|
65
|
-
Bucket: bucketName,
|
|
66
|
-
Key: key,
|
|
67
|
-
};
|
|
68
|
-
const { Body } = await this.s3.send(new GetObjectCommand(params));
|
|
69
|
-
const fileStream = createWriteStream(destinationPath);
|
|
70
|
-
|
|
71
|
-
await pipelineAsync(Body as NodeJS.ReadableStream, fileStream);
|
|
72
|
-
|
|
73
|
-
console.log(
|
|
74
|
-
`File downloaded successfully from ${bucketName}/${key} to ${destinationPath}`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
9
|
async uploadToPresignedUrl(
|
|
79
10
|
presignedUrl: string,
|
|
80
11
|
filePath: string
|
|
81
12
|
): Promise<void> {
|
|
82
13
|
try {
|
|
83
14
|
const fileStream = createReadStream(filePath);
|
|
84
|
-
const fileStats = await
|
|
85
|
-
|
|
86
|
-
const response = await
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
15
|
+
const fileStats = await fs.promises.stat(filePath);
|
|
16
|
+
|
|
17
|
+
const response = await fetch(presignedUrl, {
|
|
18
|
+
method: "PUT",
|
|
19
|
+
headers: { "Content-Length": String(fileStats.size) },
|
|
20
|
+
body: fileStream as any,
|
|
21
|
+
// @ts-ignore - Node 18+ supports ReadableStream body with duplex
|
|
22
|
+
duplex: "half",
|
|
92
23
|
});
|
|
93
24
|
|
|
94
|
-
if (response.
|
|
95
|
-
console.log("File uploaded successfully to pre-signed URL");
|
|
96
|
-
} else {
|
|
25
|
+
if (!response.ok) {
|
|
97
26
|
throw new Error(`Upload failed with status code: ${response.status}`);
|
|
98
27
|
}
|
|
28
|
+
|
|
29
|
+
console.log("File uploaded successfully to pre-signed URL");
|
|
99
30
|
} catch (error) {
|
|
100
31
|
console.error("Error uploading file to pre-signed URL:", error);
|
|
101
32
|
throw error;
|
|
@@ -107,12 +38,14 @@ export class S3Service {
|
|
|
107
38
|
destinationPath: string
|
|
108
39
|
): Promise<void> {
|
|
109
40
|
try {
|
|
110
|
-
const response = await
|
|
111
|
-
|
|
112
|
-
|
|
41
|
+
const response = await fetch(presignedUrl);
|
|
42
|
+
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new Error(`Download failed with status code: ${response.status}`);
|
|
45
|
+
}
|
|
113
46
|
|
|
114
47
|
const fileStream = createWriteStream(destinationPath);
|
|
115
|
-
await pipelineAsync(response.
|
|
48
|
+
await pipelineAsync(Readable.from(response.body as any), fileStream);
|
|
116
49
|
|
|
117
50
|
console.log(
|
|
118
51
|
`File downloaded successfully from pre-signed URL to ${destinationPath}`
|
|
@@ -123,4 +56,3 @@ export class S3Service {
|
|
|
123
56
|
}
|
|
124
57
|
}
|
|
125
58
|
}
|
|
126
|
-
|