From c6975e544cca6c5a9f948c8dcf55c9591bd3d3bb Mon Sep 17 00:00:00 2001 From: Gaute Rauboti Date: Wed, 27 Nov 2024 10:27:25 +0100 Subject: [PATCH 01/30] Add button to ImageEmbedForm --- .../plugins/image/ImageEmbedForm.tsx | 25 ++++++++++++++++--- src/phrases/phrases-en.ts | 4 +++ src/phrases/phrases-nb.ts | 4 +++ src/phrases/phrases-nn.ts | 4 +++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx b/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx index a5c909039b..4a37f8d255 100644 --- a/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx +++ b/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx @@ -7,10 +7,10 @@ */ import { Formik, useFormikContext } from "formik"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Descendant } from "slate"; -import { CheckLine } from "@ndla/icons/editor"; +import { BlogPost, CheckLine } from "@ndla/icons/editor"; import { Button, CheckboxControl, @@ -23,6 +23,7 @@ import { Text, FieldErrorMessage, FieldTextArea, + Spinner, } from "@ndla/primitives"; import { styled } from "@ndla/styled-system/jsx"; import { IImageMetaInformationV3 } from "@ndla/types-backend/image-api"; @@ -146,6 +147,12 @@ const InputWrapper = styled("div", { }, }); +const StyledButton = styled(Button, { + base: { + alignSelf: "flex-start", + }, +}); + const EmbedForm = ({ onClose, language, @@ -156,6 +163,15 @@ const EmbedForm = ({ const inGrid = useInGrid(); const { values, initialValues, isValid, setFieldValue, dirty, isSubmitting } = useFormikContext(); + const [isLoading, setIsLoading] = useState(false); + + const generateAltText = () => { + setIsLoading(true); + // Do something + setTimeout(() => { + setIsLoading(false); + }, 1000); + }; const formIsDirty = isFormikFormDirty({ values, @@ -180,13 +196,16 @@ const EmbedForm = ({ /> )} - {!values.isDecorative && ( {({ field, meta }) => ( {t("form.image.alt.label")} + + {t("textGeneration.altText.button")} + {isLoading ? : } + {meta.error} )} diff --git a/src/phrases/phrases-en.ts b/src/phrases/phrases-en.ts index be3141a2b7..4e0ddbff2a 100644 --- a/src/phrases/phrases-en.ts +++ b/src/phrases/phrases-en.ts @@ -2255,6 +2255,10 @@ const phrases = { textSuggested: "Suggested phrasing", title: "Alternative phrasing", }, + altText: { + button: "Generate", + title: "Generate alt text", + }, reflectionQuestions: { button: "Generate reflection questions", prompt: `{{ article }} diff --git a/src/phrases/phrases-nb.ts b/src/phrases/phrases-nb.ts index 25260bb61b..9e6268c022 100644 --- a/src/phrases/phrases-nb.ts +++ b/src/phrases/phrases-nb.ts @@ -2253,6 +2253,10 @@ const phrases = { textSuggested: "Forslag til ny formulering", title: "Alternativ formulering", }, + altText: { + button: "Generer", + title: "Generer alt tekst", + }, reflectionQuestions: { button: "Generer refleksjonsspørsmål", prompt: `{{ article }} diff --git a/src/phrases/phrases-nn.ts b/src/phrases/phrases-nn.ts index b6a21f0696..154a0a41ce 100644 --- a/src/phrases/phrases-nn.ts +++ b/src/phrases/phrases-nn.ts @@ -2255,6 +2255,10 @@ const phrases = { textSuggested: "Forslag til ny formulering", title: "Alternativ formulering", }, + altText: { + button: "Generer", + title: "Generer alt tekst", + }, reflectionQuestions: { button: "Generer refleksjonsspørsmål", prompt: `{{ article }} From 0df5676f03fdebae491f1622fa9c4a3694612e61 Mon Sep 17 00:00:00 2001 From: Gaute Rauboti Date: Wed, 27 Nov 2024 13:17:41 +0100 Subject: [PATCH 02/30] Merged in endpoint + connected button and textfield --- src/components/LLM/helpers.ts | 14 ++++---- .../plugins/image/ImageEmbedForm.tsx | 29 ++++++++++++---- src/phrases/phrases-en.ts | 5 +++ src/phrases/phrases-nb.ts | 5 +++ src/phrases/phrases-nn.ts | 5 +++ src/server/api.ts | 33 +++++++++++++++---- src/util/imageToBase64.ts | 28 ++++++++++++++++ 7 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 src/util/imageToBase64.ts diff --git a/src/components/LLM/helpers.ts b/src/components/LLM/helpers.ts index ff193e2a26..3103e44aa3 100644 --- a/src/components/LLM/helpers.ts +++ b/src/components/LLM/helpers.ts @@ -10,24 +10,26 @@ export const claudeHaikuDefaults = { top_p: 0.7, top_k: 100, temperature: 0.9 }; interface modelProps { prompt: string; + image?: string; max_tokens?: number; } -export const invokeModel = async ({ prompt, max_tokens = 2000, ...rest }: modelProps) => { +export const invokeModel = async ({ prompt, image, max_tokens = 2000, ...rest }: modelProps) => { if (!prompt) { console.error("No prompt provided to invokeModel"); return null; } + + const payload: any = { prompt, max_tokens, ...rest }; + if (image) { + payload.image = image; + } const response = await fetch("/invoke-model", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - prompt: prompt, - max_tokens: max_tokens, - ...rest, - }), + body: JSON.stringify(payload), }); if (!response.ok) { diff --git a/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx b/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx index 4a37f8d255..c29789a440 100644 --- a/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx +++ b/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx @@ -28,6 +28,7 @@ import { import { styled } from "@ndla/styled-system/jsx"; import { IImageMetaInformationV3 } from "@ndla/types-backend/image-api"; import { ImageEmbedData } from "@ndla/types-embed"; +import { claudeHaikuDefaults, invokeModel } from "../../../../components/LLM/helpers"; import { InlineField } from "../../../../containers/FormikForm/InlineField"; import ImageEditor from "../../../../containers/ImageEditor/ImageEditor"; import { inlineContentToEditorValue, inlineContentToHTML } from "../../../../util/articleContentConverter"; @@ -165,12 +166,19 @@ const EmbedForm = ({ useFormikContext(); const [isLoading, setIsLoading] = useState(false); - const generateAltText = () => { + const generateAltText = async () => { setIsLoading(true); - // Do something - setTimeout(() => { - setIsLoading(false); - }, 1000); + if (!image?.image.imageUrl) { + return null; + } + const result = await invokeModel({ + prompt: t("textGeneration.altText.prompt", { language: t(`languages.${language}`) }), + image: image?.image.imageUrl, + max_tokens: 2000, + ...claudeHaikuDefaults, + }); + setIsLoading(false); + return result; }; const formIsDirty = isFormikFormDirty({ @@ -198,11 +206,18 @@ const EmbedForm = ({ {!values.isDecorative && ( - {({ field, meta }) => ( + {({ field, meta, helpers }) => ( {t("form.image.alt.label")} - + { + const text = await generateAltText(); + text && text.length > 0 && helpers.setValue(text); + }} + size="small" + title={t("textGeneration.altText.title")} + > {t("textGeneration.altText.button")} {isLoading ? : } diff --git a/src/phrases/phrases-en.ts b/src/phrases/phrases-en.ts index 4e0ddbff2a..10102515c2 100644 --- a/src/phrases/phrases-en.ts +++ b/src/phrases/phrases-en.ts @@ -2257,6 +2257,11 @@ const phrases = { }, altText: { button: "Generate", + prompt: ` + You have an extensive experience from the educational sector. You are specialized in writing alternative texts for images to make them accessible for all students. + Your task is to write an alternative text for the image in {{ language }}. The description must be at most 125 characters. + The response must be given with reflections as to why you have chosen to include the selected points first, then the allternative text written in an tag. + `, title: "Generate alt text", }, reflectionQuestions: { diff --git a/src/phrases/phrases-nb.ts b/src/phrases/phrases-nb.ts index 9e6268c022..d438d42a69 100644 --- a/src/phrases/phrases-nb.ts +++ b/src/phrases/phrases-nb.ts @@ -2255,6 +2255,11 @@ const phrases = { }, altText: { button: "Generer", + prompt: ` + Du har lang erfaring fra utdanningssektoren. Du er spesialist i å skrive alternative tekster for bilder for å gjøre dem tilgjengelige for alle elever. + Du har fått som oppdrag å skrive en alternativ tekst for bildet vedlagt på {{ language }}. Beskrivelsen skal være på maks 125 tegn. + Svaret skal leveres med refleksjoner på hvorfor du har valgt å inkludere de punktene du har valgt først, så selve alternativteksten skrevet i en tag. + `, title: "Generer alt tekst", }, reflectionQuestions: { diff --git a/src/phrases/phrases-nn.ts b/src/phrases/phrases-nn.ts index 154a0a41ce..ab7b3f8589 100644 --- a/src/phrases/phrases-nn.ts +++ b/src/phrases/phrases-nn.ts @@ -2257,6 +2257,11 @@ const phrases = { }, altText: { button: "Generer", + prompt: ` + Du har lang erfaring frå utdanningssektoren. Du er spesialist i å skrive alternative tekster for bilete for å gjere dei tilgjengelege for alle elevar. + Du har fått som oppgåve å skrive en alternativ tekst for bilete vedlagt på {{ language }}. Beskrivinga skal vere på maks 125 teikn. + Svaret skal leverast med refleksjonar på kvifor du har valt å inkludere dei punktane du har valt først, så selve alternativteksten skrevet i ein tag. + `, title: "Generer alt tekst", }, reflectionQuestions: { diff --git a/src/server/api.ts b/src/server/api.ts index ca5b4dd19d..2e0493f698 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -18,6 +18,7 @@ import { translateDocument } from "./translate"; import config, { getEnvironmentVariabel } from "../config"; import { DRAFT_PUBLISH_SCOPE, DRAFT_WRITE_SCOPE } from "../constants"; import { NdlaError } from "../interfaces"; +import { imageToBase64 } from "../util/imageToBase64"; const router = express.Router(); @@ -169,15 +170,35 @@ router.post("/invoke-model", async (req, res) => { credentials: { accessKeyId: secretId, secretAccessKey: secretKey }, }); + const content = []; + if (req.body.image) { + const { base64, filetype } = await imageToBase64(req.body.image); + content.push({ + type: "image", + source: { + type: "base64", + media_type: filetype, + data: base64, + }, + }); + } + + content.push({ + type: "text", + text: req.body.prompt, + }); + + const messages = [ + { + role: "user", + content, + }, + ]; + const payload = { anthropic_version: "bedrock-2023-05-31", max_tokens: req.body.max_tokens || 500, - messages: [ - { - role: ConversationRole.USER, - content: [{ type: "text", text: req.body.prompt }], - }, - ], + messages, }; const command = new InvokeModelCommand({ contentType: "application/json", diff --git a/src/util/imageToBase64.ts b/src/util/imageToBase64.ts new file mode 100644 index 0000000000..1d2671ed25 --- /dev/null +++ b/src/util/imageToBase64.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2024-present, NDLA. + * + * This source code is licensed under the GPLv3 license found in the + * LICENSE file in the root directory of this source tree. + * + */ +export const imageToBase64 = async (url: string) => { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.statusText}`); + } + + const contentType = response.headers.get("Content-Type"); + const buffer = await response.arrayBuffer(); + + if (!contentType) { + throw new Error("Failed to determine file type"); + } + + // Convert buffer to Base64 + const base64 = Buffer.from(buffer).toString("base64"); + + return { + base64, + filetype: contentType, + }; +}; From 7492b7b08fac02f4b35d2e68de48cbaf233ec90c Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Fri, 29 Nov 2024 12:05:28 +0100 Subject: [PATCH 03/30] Add transcribe and get_transcription endpoints --- package.json | 1 + src/components/Transcribe/helpers.ts | 44 ++ src/server/api.ts | 69 +++ yarn.lock | 824 +++++++++++++++++++++++++++ 4 files changed, 938 insertions(+) create mode 100644 src/components/Transcribe/helpers.ts diff --git a/package.json b/package.json index b218288b54..2b2e2efc34 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "dependencies": { "@ark-ui/react": "^4.1.2", "@aws-sdk/client-bedrock-runtime": "^3.670.0", + "@aws-sdk/client-transcribe": "^3.699.0", "@dnd-kit/core": "^6.0.8", "@dnd-kit/modifiers": "^6.0.1", "@dnd-kit/sortable": "^7.0.2", diff --git a/src/components/Transcribe/helpers.ts b/src/components/Transcribe/helpers.ts new file mode 100644 index 0000000000..205b5e848a --- /dev/null +++ b/src/components/Transcribe/helpers.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2024-present, NDLA. + * + * This source code is licensed under the GPLv3 license found in the + * LICENSE file in the root directory of this source tree. + * + */ +import { GetTranscriptionJobCommand } from "@aws-sdk/client-transcribe"; + +/** + * Copyright (c) 2024-present, NDLA. + * + * This source code is licensed under the GPLv3 license found in the + * LICENSE file in the root directory of this source tree. + * + */ +interface props { + fileUrl: string; + languageCode: string; + mediaFormat: string; + maxSpeakers?: number; + outputFileName: string; +} + +export const transcribe = async ({ fileUrl, maxSpeakers, mediaFormat, languageCode, outputFileName }: props) => { + const payload: any = { + mediaFileUri: fileUrl, + languageCode: languageCode, + mediaFormat: mediaFormat, + outputFileName: outputFileName, + }; + + if (maxSpeakers) { + payload.maxSpeakers = maxSpeakers; + } + + const response = await fetch("/transcribe", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); +}; diff --git a/src/server/api.ts b/src/server/api.ts index ca5b4dd19d..d21bde64d7 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -11,6 +11,7 @@ import { GetVerificationKey, expressjwt as jwt, Request } from "express-jwt"; import jwksRsa from "jwks-rsa"; import prettier from "prettier"; import { BedrockRuntimeClient, ConversationRole, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime"; +import { TranscribeClient, StartTranscriptionJobCommand, GetTranscriptionJobCommand } from "@aws-sdk/client-transcribe"; import { getToken, getBrightcoveToken, fetchAuth0UsersById, getEditors, getResponsibles } from "./auth"; import { OK, INTERNAL_SERVER_ERROR, NOT_ACCEPTABLE, FORBIDDEN } from "./httpCodes"; import errorLogger from "./logger"; @@ -193,4 +194,72 @@ router.post("/invoke-model", async (req, res) => { res.status(INTERNAL_SERVER_ERROR).send((err as NdlaError).message); } }); + +router.post("/transcribe", async (req, res) => { + if (!req.body.languageCode || !req.body.mediaFormat || !req.body.mediaFileUri || !req.body.outputFileName) { + res.status(400).send("Missing required parameters"); + } + + const client = new TranscribeClient({ + region: "eu-west-1", + }); + + const jobName = `transcribe-${Date.now()}`; + const command = new StartTranscriptionJobCommand({ + TranscriptionJobName: jobName, + LanguageCode: req.body.languageCode, + MediaFormat: req.body.mediaFormat, + Media: { + MediaFileUri: req.body.mediaFileUri, + }, + OutputBucketName: getEnvironmentVariabel("S3_TRANSCRIPTION_BUCKET_NAME") || "test.transcribe.ndla", + OutputKey: req.body.outputFileName, + Settings: { + ShowSpeakerLabels: true, // Enable speaker identification + MaxSpeakerLabels: req.body.maxSpeakers || 2, + }, + }); + try { + const response = await client.send(command); + res.status(OK).json(response); + } catch (err) { + res.status(INTERNAL_SERVER_ERROR).send((err as NdlaError).message); + } +}); + +router.post("/get_transcription", async (req, res) => { + const jobName = req.body.jobName; + + if (!jobName) { + res.status(404).send(""); + } + + const client = new TranscribeClient({ + region: "eu-west-1", + }); + + try { + const command = new GetTranscriptionJobCommand({ TranscriptionJobName: jobName }); + const response = await client.send(command); + + if (!response || !response.TranscriptionJob) { + res.status(404).send({ error: "Job not found or an error occurred" }); + return; + } + const jobStatus = response.TranscriptionJob.TranscriptionJobStatus; + + if (jobStatus === "COMPLETED") { + const transcriptUri = response.TranscriptionJob.Transcript?.TranscriptFileUri || ""; + res.json({ jobName, status: "COMPLETED", transcriptUrl: transcriptUri }); + } else if (jobStatus === "FAILED") { + res.status(404).send({ jobName, status: "FAILED", reason: response.TranscriptionJob.FailureReason }); + } else { + res.json({ jobName, status: jobStatus }); + } + } catch (error) { + console.error("Error fetching job status:", error); + res.status(INTERNAL_SERVER_ERROR).send((error as NdlaError).message); + } +}); + export default router; diff --git a/yarn.lock b/yarn.lock index 392506753a..81f6ab24d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -248,6 +248,55 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sso-oidc@npm:3.699.0": + version: 3.699.0 + resolution: "@aws-sdk/client-sso-oidc@npm:3.699.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/credential-provider-node": "npm:3.699.0" + "@aws-sdk/middleware-host-header": "npm:3.696.0" + "@aws-sdk/middleware-logger": "npm:3.696.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.696.0" + "@aws-sdk/middleware-user-agent": "npm:3.696.0" + "@aws-sdk/region-config-resolver": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@aws-sdk/util-endpoints": "npm:3.696.0" + "@aws-sdk/util-user-agent-browser": "npm:3.696.0" + "@aws-sdk/util-user-agent-node": "npm:3.696.0" + "@smithy/config-resolver": "npm:^3.0.12" + "@smithy/core": "npm:^2.5.3" + "@smithy/fetch-http-handler": "npm:^4.1.1" + "@smithy/hash-node": "npm:^3.0.10" + "@smithy/invalid-dependency": "npm:^3.0.10" + "@smithy/middleware-content-length": "npm:^3.0.12" + "@smithy/middleware-endpoint": "npm:^3.2.3" + "@smithy/middleware-retry": "npm:^3.0.27" + "@smithy/middleware-serde": "npm:^3.0.10" + "@smithy/middleware-stack": "npm:^3.0.10" + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/node-http-handler": "npm:^3.3.1" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/smithy-client": "npm:^3.4.4" + "@smithy/types": "npm:^3.7.1" + "@smithy/url-parser": "npm:^3.0.10" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-body-length-node": "npm:^3.0.0" + "@smithy/util-defaults-mode-browser": "npm:^3.0.27" + "@smithy/util-defaults-mode-node": "npm:^3.0.27" + "@smithy/util-endpoints": "npm:^2.1.6" + "@smithy/util-middleware": "npm:^3.0.10" + "@smithy/util-retry": "npm:^3.0.10" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + peerDependencies: + "@aws-sdk/client-sts": ^3.699.0 + checksum: 10c0/b4d277fe4a7af3934b7528e8379901ae56ab9b9d3b6ed019d0a89f22ce2f8430edbe77ef1ced413216b378e517e32a625f3c4a4e8d6ef2bc58baefb6bc5e96d9 + languageName: node + linkType: hard + "@aws-sdk/client-sso@npm:3.670.0": version: 3.670.0 resolution: "@aws-sdk/client-sso@npm:3.670.0" @@ -294,6 +343,52 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sso@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/client-sso@npm:3.696.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/middleware-host-header": "npm:3.696.0" + "@aws-sdk/middleware-logger": "npm:3.696.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.696.0" + "@aws-sdk/middleware-user-agent": "npm:3.696.0" + "@aws-sdk/region-config-resolver": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@aws-sdk/util-endpoints": "npm:3.696.0" + "@aws-sdk/util-user-agent-browser": "npm:3.696.0" + "@aws-sdk/util-user-agent-node": "npm:3.696.0" + "@smithy/config-resolver": "npm:^3.0.12" + "@smithy/core": "npm:^2.5.3" + "@smithy/fetch-http-handler": "npm:^4.1.1" + "@smithy/hash-node": "npm:^3.0.10" + "@smithy/invalid-dependency": "npm:^3.0.10" + "@smithy/middleware-content-length": "npm:^3.0.12" + "@smithy/middleware-endpoint": "npm:^3.2.3" + "@smithy/middleware-retry": "npm:^3.0.27" + "@smithy/middleware-serde": "npm:^3.0.10" + "@smithy/middleware-stack": "npm:^3.0.10" + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/node-http-handler": "npm:^3.3.1" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/smithy-client": "npm:^3.4.4" + "@smithy/types": "npm:^3.7.1" + "@smithy/url-parser": "npm:^3.0.10" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-body-length-node": "npm:^3.0.0" + "@smithy/util-defaults-mode-browser": "npm:^3.0.27" + "@smithy/util-defaults-mode-node": "npm:^3.0.27" + "@smithy/util-endpoints": "npm:^2.1.6" + "@smithy/util-middleware": "npm:^3.0.10" + "@smithy/util-retry": "npm:^3.0.10" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/e96c907c3385ea183181eb7dbdceb01c2b96a220f67bf6147b9a116aa197ceb2860fa54667405a7f60f365ee1c056b7039ff1ac236815894b675ee76c52862f3 + languageName: node + linkType: hard + "@aws-sdk/client-sts@npm:3.670.0": version: 3.670.0 resolution: "@aws-sdk/client-sts@npm:3.670.0" @@ -342,6 +437,103 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sts@npm:3.699.0": + version: 3.699.0 + resolution: "@aws-sdk/client-sts@npm:3.699.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/client-sso-oidc": "npm:3.699.0" + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/credential-provider-node": "npm:3.699.0" + "@aws-sdk/middleware-host-header": "npm:3.696.0" + "@aws-sdk/middleware-logger": "npm:3.696.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.696.0" + "@aws-sdk/middleware-user-agent": "npm:3.696.0" + "@aws-sdk/region-config-resolver": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@aws-sdk/util-endpoints": "npm:3.696.0" + "@aws-sdk/util-user-agent-browser": "npm:3.696.0" + "@aws-sdk/util-user-agent-node": "npm:3.696.0" + "@smithy/config-resolver": "npm:^3.0.12" + "@smithy/core": "npm:^2.5.3" + "@smithy/fetch-http-handler": "npm:^4.1.1" + "@smithy/hash-node": "npm:^3.0.10" + "@smithy/invalid-dependency": "npm:^3.0.10" + "@smithy/middleware-content-length": "npm:^3.0.12" + "@smithy/middleware-endpoint": "npm:^3.2.3" + "@smithy/middleware-retry": "npm:^3.0.27" + "@smithy/middleware-serde": "npm:^3.0.10" + "@smithy/middleware-stack": "npm:^3.0.10" + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/node-http-handler": "npm:^3.3.1" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/smithy-client": "npm:^3.4.4" + "@smithy/types": "npm:^3.7.1" + "@smithy/url-parser": "npm:^3.0.10" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-body-length-node": "npm:^3.0.0" + "@smithy/util-defaults-mode-browser": "npm:^3.0.27" + "@smithy/util-defaults-mode-node": "npm:^3.0.27" + "@smithy/util-endpoints": "npm:^2.1.6" + "@smithy/util-middleware": "npm:^3.0.10" + "@smithy/util-retry": "npm:^3.0.10" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/bdc7bc373fc518570d8d034b6e1af033c2bf272217c79ebe3e1ec3f928c5b73b4b71f6b7d0be9a95db1f909cdcbe8b5a52776f4f2290d63a78bd05ece7d9abe0 + languageName: node + linkType: hard + +"@aws-sdk/client-transcribe@npm:^3.699.0": + version: 3.699.0 + resolution: "@aws-sdk/client-transcribe@npm:3.699.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/client-sso-oidc": "npm:3.699.0" + "@aws-sdk/client-sts": "npm:3.699.0" + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/credential-provider-node": "npm:3.699.0" + "@aws-sdk/middleware-host-header": "npm:3.696.0" + "@aws-sdk/middleware-logger": "npm:3.696.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.696.0" + "@aws-sdk/middleware-user-agent": "npm:3.696.0" + "@aws-sdk/region-config-resolver": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@aws-sdk/util-endpoints": "npm:3.696.0" + "@aws-sdk/util-user-agent-browser": "npm:3.696.0" + "@aws-sdk/util-user-agent-node": "npm:3.696.0" + "@smithy/config-resolver": "npm:^3.0.12" + "@smithy/core": "npm:^2.5.3" + "@smithy/fetch-http-handler": "npm:^4.1.1" + "@smithy/hash-node": "npm:^3.0.10" + "@smithy/invalid-dependency": "npm:^3.0.10" + "@smithy/middleware-content-length": "npm:^3.0.12" + "@smithy/middleware-endpoint": "npm:^3.2.3" + "@smithy/middleware-retry": "npm:^3.0.27" + "@smithy/middleware-serde": "npm:^3.0.10" + "@smithy/middleware-stack": "npm:^3.0.10" + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/node-http-handler": "npm:^3.3.1" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/smithy-client": "npm:^3.4.4" + "@smithy/types": "npm:^3.7.1" + "@smithy/url-parser": "npm:^3.0.10" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-body-length-node": "npm:^3.0.0" + "@smithy/util-defaults-mode-browser": "npm:^3.0.27" + "@smithy/util-defaults-mode-node": "npm:^3.0.27" + "@smithy/util-endpoints": "npm:^2.1.6" + "@smithy/util-middleware": "npm:^3.0.10" + "@smithy/util-retry": "npm:^3.0.10" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/d6f126b1184473c3571e5654320781b09af2101b299738cf0382c1a3fd6b2f79c5986c66f3a00e57a78402b6463199dfdd3d5dc4c12fe2baf873ce10c95466bb + languageName: node + linkType: hard + "@aws-sdk/core@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/core@npm:3.667.0" @@ -361,6 +553,25 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/core@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/core@npm:3.696.0" + dependencies: + "@aws-sdk/types": "npm:3.696.0" + "@smithy/core": "npm:^2.5.3" + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/property-provider": "npm:^3.1.9" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/signature-v4": "npm:^4.2.2" + "@smithy/smithy-client": "npm:^3.4.4" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-middleware": "npm:^3.0.10" + fast-xml-parser: "npm:4.4.1" + tslib: "npm:^2.6.2" + checksum: 10c0/4a96a3e29bf6e0dcd82d8160633eb4b8a488d821a8e59c1033c79a8e0d32b2f82e241e1cf94599f48836800549e342a410318b18e055851741ddf7d5d3ad4606 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-env@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/credential-provider-env@npm:3.667.0" @@ -374,6 +585,19 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-env@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.696.0" + dependencies: + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@smithy/property-provider": "npm:^3.1.9" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/e16987ca343f9dbae2560d0d016ca005f36b27fb094f8d32b99954d0a2874aa8230f924f2dab2a0e0aebc7ee9eda6881c5f6e928d89dc759f70a7658363e20be + languageName: node + linkType: hard + "@aws-sdk/credential-provider-http@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/credential-provider-http@npm:3.667.0" @@ -392,6 +616,24 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-http@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.696.0" + dependencies: + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@smithy/fetch-http-handler": "npm:^4.1.1" + "@smithy/node-http-handler": "npm:^3.3.1" + "@smithy/property-provider": "npm:^3.1.9" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/smithy-client": "npm:^3.4.4" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-stream": "npm:^3.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/383dd45600b0edbcc52c8e1101485569e631fb218f1776bbd4971e43a54be0adef458cb096d06d944353675136d2043da588424c78fff1c4eeeaf5229eb6774d + languageName: node + linkType: hard + "@aws-sdk/credential-provider-ini@npm:3.670.0": version: 3.670.0 resolution: "@aws-sdk/credential-provider-ini@npm:3.670.0" @@ -414,6 +656,28 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-ini@npm:3.699.0": + version: 3.699.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.699.0" + dependencies: + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/credential-provider-env": "npm:3.696.0" + "@aws-sdk/credential-provider-http": "npm:3.696.0" + "@aws-sdk/credential-provider-process": "npm:3.696.0" + "@aws-sdk/credential-provider-sso": "npm:3.699.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@smithy/credential-provider-imds": "npm:^3.2.6" + "@smithy/property-provider": "npm:^3.1.9" + "@smithy/shared-ini-file-loader": "npm:^3.1.10" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + peerDependencies: + "@aws-sdk/client-sts": ^3.699.0 + checksum: 10c0/1efb837da910ce4e8a43574f2fdceb82daecefbb7f3853d7ec97059a80a7193cf579d185d4f4b1ef67cb378db9c5d4d3058a252a75fd6a32caad257c6602765e + languageName: node + linkType: hard + "@aws-sdk/credential-provider-node@npm:3.670.0": version: 3.670.0 resolution: "@aws-sdk/credential-provider-node@npm:3.670.0" @@ -434,6 +698,26 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-node@npm:3.699.0": + version: 3.699.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.699.0" + dependencies: + "@aws-sdk/credential-provider-env": "npm:3.696.0" + "@aws-sdk/credential-provider-http": "npm:3.696.0" + "@aws-sdk/credential-provider-ini": "npm:3.699.0" + "@aws-sdk/credential-provider-process": "npm:3.696.0" + "@aws-sdk/credential-provider-sso": "npm:3.699.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@smithy/credential-provider-imds": "npm:^3.2.6" + "@smithy/property-provider": "npm:^3.1.9" + "@smithy/shared-ini-file-loader": "npm:^3.1.10" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/d2e690eb839d906409da293af7918ab20210c25428985f019b161b3cbf5deca681d4cc397c7d5a929aeaa0b90be8dbe3282bd5a9b17969c2e6ddb5c08d66e5c4 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-process@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/credential-provider-process@npm:3.667.0" @@ -448,6 +732,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-process@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.696.0" + dependencies: + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@smithy/property-provider": "npm:^3.1.9" + "@smithy/shared-ini-file-loader": "npm:^3.1.10" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/61741aa3d9cbbc88ea31bad7b7e8253aa4a0860eef215ff8d9a8196cdaa7ca8fa3bb438500c558abc9ce78b9490c540b12180acee21a7a9276491344931c5279 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-sso@npm:3.670.0": version: 3.670.0 resolution: "@aws-sdk/credential-provider-sso@npm:3.670.0" @@ -464,6 +762,22 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-sso@npm:3.699.0": + version: 3.699.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.699.0" + dependencies: + "@aws-sdk/client-sso": "npm:3.696.0" + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/token-providers": "npm:3.699.0" + "@aws-sdk/types": "npm:3.696.0" + "@smithy/property-provider": "npm:^3.1.9" + "@smithy/shared-ini-file-loader": "npm:^3.1.10" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/be78a04f971d716b24e4bb9ce5ecec8ed8ffe9fdeebb07d4e6138c1b833529b5260d7381af8460b00f1659eb26018bffa51c9955b24a327374dd79c2fb2ce0ab + languageName: node + linkType: hard + "@aws-sdk/credential-provider-web-identity@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/credential-provider-web-identity@npm:3.667.0" @@ -479,6 +793,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-web-identity@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.696.0" + dependencies: + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@smithy/property-provider": "npm:^3.1.9" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + peerDependencies: + "@aws-sdk/client-sts": ^3.696.0 + checksum: 10c0/a983867c72a6c8a1fd397f8051f4b6e64f5cac1ff5afff1b2d00815096d6c819d9ad155f4724cb27ebe3c13714eeb22cc545533f4ccaaa63980308b8bef2fa4c + languageName: node + linkType: hard + "@aws-sdk/middleware-host-header@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/middleware-host-header@npm:3.667.0" @@ -491,6 +820,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-host-header@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.696.0" + dependencies: + "@aws-sdk/types": "npm:3.696.0" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/793c61a6af5533872888d9ee1b6765e06bd9716a9b1e497fb53b39da0bdbde2c379601ddf29bd2120cc520241143bae7763691f476f81721c290ee4e71264b6e + languageName: node + linkType: hard + "@aws-sdk/middleware-logger@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/middleware-logger@npm:3.667.0" @@ -502,6 +843,17 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-logger@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/middleware-logger@npm:3.696.0" + dependencies: + "@aws-sdk/types": "npm:3.696.0" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/978145de80cb21a59d525fe9611d78e513df506e29123c39d425dd7c77043f9b57f05f03edde33864d9494a7ce76b7e2a48ec38ee4cee213b470ff1cd11c229f + languageName: node + linkType: hard + "@aws-sdk/middleware-recursion-detection@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/middleware-recursion-detection@npm:3.667.0" @@ -514,6 +866,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-recursion-detection@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.696.0" + dependencies: + "@aws-sdk/types": "npm:3.696.0" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/20db668ef267c62134e241511a6a5a49cbcacbf4eb28eb8fede903086e38bdc3d6d5277f5faae4bb0b3a5123a2f1c116b219c3c48d4b8aa49c12e97707736d51 + languageName: node + linkType: hard + "@aws-sdk/middleware-user-agent@npm:3.669.0": version: 3.669.0 resolution: "@aws-sdk/middleware-user-agent@npm:3.669.0" @@ -529,6 +893,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-user-agent@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.696.0" + dependencies: + "@aws-sdk/core": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@aws-sdk/util-endpoints": "npm:3.696.0" + "@smithy/core": "npm:^2.5.3" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/3af4fc987d3a3cfa9036c67f60fb939a02d801ccb2781ea0be653896dfb34382c4c895a2e3ce2c48f2db547aea09d871217d77c814331251faf10b5a472974f7 + languageName: node + linkType: hard + "@aws-sdk/region-config-resolver@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/region-config-resolver@npm:3.667.0" @@ -543,6 +922,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/region-config-resolver@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/region-config-resolver@npm:3.696.0" + dependencies: + "@aws-sdk/types": "npm:3.696.0" + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-config-provider": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.10" + tslib: "npm:^2.6.2" + checksum: 10c0/bc8765735dcd888a73336d1c0cac75fec0303446f2cd97c7818cec89d5d9f7e4b98705de1e751a47abbc3442d9237169dc967f175be27d9f828e65acb6c2d23a + languageName: node + linkType: hard + "@aws-sdk/token-providers@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/token-providers@npm:3.667.0" @@ -558,6 +951,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/token-providers@npm:3.699.0": + version: 3.699.0 + resolution: "@aws-sdk/token-providers@npm:3.699.0" + dependencies: + "@aws-sdk/types": "npm:3.696.0" + "@smithy/property-provider": "npm:^3.1.9" + "@smithy/shared-ini-file-loader": "npm:^3.1.10" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + peerDependencies: + "@aws-sdk/client-sso-oidc": ^3.699.0 + checksum: 10c0/f69d005aff7e85d04930374651edb75937cadab5baaa365044bf1318207b208d7cf857142fdbb8e66055fb92043140531945986346661bc82322b7307b109d56 + languageName: node + linkType: hard + "@aws-sdk/types@npm:3.667.0, @aws-sdk/types@npm:^3.222.0": version: 3.667.0 resolution: "@aws-sdk/types@npm:3.667.0" @@ -568,6 +976,16 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/types@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/types@npm:3.696.0" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/3721939d5dd2a68fa4aee89d56b4817dd6c020721e2b2ea5b702968e7055826eb37e1924bc298007686304bf9bb6623bfec26b5cfd0663f2dba9d1b48437bb91 + languageName: node + linkType: hard + "@aws-sdk/util-endpoints@npm:3.667.0": version: 3.667.0 resolution: "@aws-sdk/util-endpoints@npm:3.667.0" @@ -580,6 +998,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-endpoints@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/util-endpoints@npm:3.696.0" + dependencies: + "@aws-sdk/types": "npm:3.696.0" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-endpoints": "npm:^2.1.6" + tslib: "npm:^2.6.2" + checksum: 10c0/b32822b5f6924b8e3f88c7269afb216d07eccb338627a366ff3f94d98e7f5e4a9448dcf7c5ac97fc31fd0dfec5dfec52bbbeda65d84edd33fd509ed1dbfb1993 + languageName: node + linkType: hard + "@aws-sdk/util-locate-window@npm:^3.0.0": version: 3.568.0 resolution: "@aws-sdk/util-locate-window@npm:3.568.0" @@ -601,6 +1031,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-user-agent-browser@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.696.0" + dependencies: + "@aws-sdk/types": "npm:3.696.0" + "@smithy/types": "npm:^3.7.1" + bowser: "npm:^2.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/e72e35b21e6945d8a3cc46f92a5a6509842fe5439c2b1628f72d1f0932398d4aae2648c8a1779e2936aa4f4720047344790dc533f334ae18b20a43443d4a7b93 + languageName: node + linkType: hard + "@aws-sdk/util-user-agent-node@npm:3.669.0": version: 3.669.0 resolution: "@aws-sdk/util-user-agent-node@npm:3.669.0" @@ -619,6 +1061,24 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-user-agent-node@npm:3.696.0": + version: 3.696.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.696.0" + dependencies: + "@aws-sdk/middleware-user-agent": "npm:3.696.0" + "@aws-sdk/types": "npm:3.696.0" + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + checksum: 10c0/9dd7ef236ff13552f559d0e78bfffe424032dc4040306808542a2eedbe80801ae05389c415b770461b6b39a0b35cdbebf97e673e6f7132e05121708acee3db83 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.24.2": version: 7.26.2 resolution: "@babel/code-frame@npm:7.26.2" @@ -3094,6 +3554,29 @@ __metadata: languageName: node linkType: hard +"@smithy/abort-controller@npm:^3.1.8": + version: 3.1.8 + resolution: "@smithy/abort-controller@npm:3.1.8" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/ba62148955592036502880ac68a3fd1d4b0b70e3ace36ef9f1d0f507287795875598e2b9823ab6cdf542dcdb9fe75b57872694fc4a8108f7ab71938426a1c89c + languageName: node + linkType: hard + +"@smithy/config-resolver@npm:^3.0.12": + version: 3.0.12 + resolution: "@smithy/config-resolver@npm:3.0.12" + dependencies: + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-config-provider": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.10" + tslib: "npm:^2.6.2" + checksum: 10c0/01686446680e1a0e98051034671813f2ea78664ee8a6b22811a12fb937c1ac5b67b63ab9a6ae5995c61991344fbacebc906189cd063512ef1c1bdfb6c491941d + languageName: node + linkType: hard + "@smithy/config-resolver@npm:^3.0.9": version: 3.0.9 resolution: "@smithy/config-resolver@npm:3.0.9" @@ -3125,6 +3608,22 @@ __metadata: languageName: node linkType: hard +"@smithy/core@npm:^2.5.3, @smithy/core@npm:^2.5.4": + version: 2.5.4 + resolution: "@smithy/core@npm:2.5.4" + dependencies: + "@smithy/middleware-serde": "npm:^3.0.10" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-body-length-browser": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.10" + "@smithy/util-stream": "npm:^3.3.1" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/b966d6a7136cc9575370a75ad380fc27b85e83dd6615c04a413a3ef7ef2a496adb1a7e46b8daa256cfaf5993c4d14957834a1dfd416a3bb16402d6012229e2a0 + languageName: node + linkType: hard + "@smithy/credential-provider-imds@npm:^3.2.4": version: 3.2.4 resolution: "@smithy/credential-provider-imds@npm:3.2.4" @@ -3138,6 +3637,19 @@ __metadata: languageName: node linkType: hard +"@smithy/credential-provider-imds@npm:^3.2.6, @smithy/credential-provider-imds@npm:^3.2.7": + version: 3.2.7 + resolution: "@smithy/credential-provider-imds@npm:3.2.7" + dependencies: + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/property-provider": "npm:^3.1.10" + "@smithy/types": "npm:^3.7.1" + "@smithy/url-parser": "npm:^3.0.10" + tslib: "npm:^2.6.2" + checksum: 10c0/c0f1d0c439f26d046ef130057ea1727cb06cab96054ed23202d6eb7eaec3e5d8ef96380b69fbdec505c569e5f2b56ed68ba8c687f47d7d99607c30e5f6e469c1 + languageName: node + linkType: hard + "@smithy/eventstream-codec@npm:^3.1.6": version: 3.1.6 resolution: "@smithy/eventstream-codec@npm:3.1.6" @@ -3206,6 +3718,31 @@ __metadata: languageName: node linkType: hard +"@smithy/fetch-http-handler@npm:^4.1.1": + version: 4.1.1 + resolution: "@smithy/fetch-http-handler@npm:4.1.1" + dependencies: + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/querystring-builder": "npm:^3.0.10" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-base64": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/e6307dfdb621a5481e7b263e2ad0a6c4b54982504c0c1ed8e2cd12d0b9b09dd99d0a7e4ebff9d8f30f1935bae24945f44cef98eca42ad119e4f1f23507ebb081 + languageName: node + linkType: hard + +"@smithy/hash-node@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/hash-node@npm:3.0.10" + dependencies: + "@smithy/types": "npm:^3.7.1" + "@smithy/util-buffer-from": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/1134872f7c4ba2c35583bd0932bf0b8cb99f5f24e79235660a5e0e0914c1d587c0ee7d44d5d4a8c0ed0c77249fc3a154d28a994dc2f42e27cf212d2052a5d0bd + languageName: node + linkType: hard + "@smithy/hash-node@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/hash-node@npm:3.0.7" @@ -3218,6 +3755,16 @@ __metadata: languageName: node linkType: hard +"@smithy/invalid-dependency@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/invalid-dependency@npm:3.0.10" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/98bae16110f3f895991c1bd0a4291d9c900380b159c6d50d7327bd5161469f63510209ea3b08cfb0a12a66dfd9de8a1dc1ac71708b68f97c06b4ee6a2cde60b7 + languageName: node + linkType: hard + "@smithy/invalid-dependency@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/invalid-dependency@npm:3.0.7" @@ -3246,6 +3793,17 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-content-length@npm:^3.0.12": + version: 3.0.12 + resolution: "@smithy/middleware-content-length@npm:3.0.12" + dependencies: + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/6d8db9bc97e3c09133ec9dc3114ca3e9ad3db5c234a2e109c3010e8661b488b08b8b2066bb2cd13da11d6ccffb9bbfbec1fa1552386d6e0d8d433b5041a6978b + languageName: node + linkType: hard + "@smithy/middleware-content-length@npm:^3.0.9": version: 3.0.9 resolution: "@smithy/middleware-content-length@npm:3.0.9" @@ -3272,6 +3830,22 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-endpoint@npm:^3.2.3, @smithy/middleware-endpoint@npm:^3.2.4": + version: 3.2.4 + resolution: "@smithy/middleware-endpoint@npm:3.2.4" + dependencies: + "@smithy/core": "npm:^2.5.4" + "@smithy/middleware-serde": "npm:^3.0.10" + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/shared-ini-file-loader": "npm:^3.1.11" + "@smithy/types": "npm:^3.7.1" + "@smithy/url-parser": "npm:^3.0.10" + "@smithy/util-middleware": "npm:^3.0.10" + tslib: "npm:^2.6.2" + checksum: 10c0/3d7f6322e26cc05e0ecdfa19a7fdf422fdddc2816b109a84a76b947e688c2a1c03e08a43434f660cc568b00114628b5b0f50b45a6b6bf95501aeb7d55cdef461 + languageName: node + linkType: hard + "@smithy/middleware-retry@npm:^3.0.23": version: 3.0.23 resolution: "@smithy/middleware-retry@npm:3.0.23" @@ -3289,6 +3863,33 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-retry@npm:^3.0.27": + version: 3.0.28 + resolution: "@smithy/middleware-retry@npm:3.0.28" + dependencies: + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/service-error-classification": "npm:^3.0.10" + "@smithy/smithy-client": "npm:^3.4.5" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-middleware": "npm:^3.0.10" + "@smithy/util-retry": "npm:^3.0.10" + tslib: "npm:^2.6.2" + uuid: "npm:^9.0.1" + checksum: 10c0/e2d4cf85a161ca711d4a6e9be420d2e9ae387d21d10ed68db2dbba9a5a76fdf6df03a16bfd9309075ea846661a7c292d073ad444cee82367a4389b12f543facc + languageName: node + linkType: hard + +"@smithy/middleware-serde@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/middleware-serde@npm:3.0.10" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/407ddbbf856c54ba5592b76aeeadc5a09a679614e8eaac91b8d662b6bd7e9cf16b60190eb15254befd34311ac137260c00433ac9126a734c6c60a256e55c0e69 + languageName: node + linkType: hard + "@smithy/middleware-serde@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/middleware-serde@npm:3.0.7" @@ -3299,6 +3900,16 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-stack@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/middleware-stack@npm:3.0.10" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/badcc1d275f7fd4957b6bce4e917060f971a4199e717cde7d3b4909be5d40e61c93328e2968e6885b4e8f7f5772e84ac743ddcc80031ab52efb47a3a3168beb0 + languageName: node + linkType: hard + "@smithy/middleware-stack@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/middleware-stack@npm:3.0.7" @@ -3309,6 +3920,18 @@ __metadata: languageName: node linkType: hard +"@smithy/node-config-provider@npm:^3.1.11": + version: 3.1.11 + resolution: "@smithy/node-config-provider@npm:3.1.11" + dependencies: + "@smithy/property-provider": "npm:^3.1.10" + "@smithy/shared-ini-file-loader": "npm:^3.1.11" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/b80a6d3f96979696499b27155c3e075f139fa6be6a2ea9688735bd1802f22bb41be4545dac9ea4db51519d22c6fb469e5bfad9063e2fa2b8771130d2f2d611a7 + languageName: node + linkType: hard + "@smithy/node-config-provider@npm:^3.1.8": version: 3.1.8 resolution: "@smithy/node-config-provider@npm:3.1.8" @@ -3334,6 +3957,29 @@ __metadata: languageName: node linkType: hard +"@smithy/node-http-handler@npm:^3.3.1": + version: 3.3.1 + resolution: "@smithy/node-http-handler@npm:3.3.1" + dependencies: + "@smithy/abort-controller": "npm:^3.1.8" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/querystring-builder": "npm:^3.0.10" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/32bb521a6cc7692ee33a362256661dbdccedfe448f116595bf6870f5c4343e3152daf5f9ae0b43d4a888016ea9161375858046f141513fb1d6c61545572712fc + languageName: node + linkType: hard + +"@smithy/property-provider@npm:^3.1.10, @smithy/property-provider@npm:^3.1.9": + version: 3.1.10 + resolution: "@smithy/property-provider@npm:3.1.10" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/8dfcf30565b00287fd3c5ad2784f5c820264251dc9d1ac7334a224e40eb3eac4762a6198961d3e261bbcc738fc0c7c88ebd1007761e994569342f339ff503e1e + languageName: node + linkType: hard + "@smithy/property-provider@npm:^3.1.7": version: 3.1.7 resolution: "@smithy/property-provider@npm:3.1.7" @@ -3354,6 +4000,27 @@ __metadata: languageName: node linkType: hard +"@smithy/protocol-http@npm:^4.1.7": + version: 4.1.7 + resolution: "@smithy/protocol-http@npm:4.1.7" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/1d5bf3e3ae9b3c7b58934163f56364228a42d50dcc64c83855be846d46f4954ed36b1bc3d949cd24bb5da3787d9b787637cffa5e3fdbbe8e1932e05ea14eace6 + languageName: node + linkType: hard + +"@smithy/querystring-builder@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/querystring-builder@npm:3.0.10" + dependencies: + "@smithy/types": "npm:^3.7.1" + "@smithy/util-uri-escape": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/3a95519ee41f195c3b56978803d50ba2b5b2ce46fc0de063442cdab347528cd0e3c3d5cd0361bc33ceeec1893198cb3246c201026c3917349e0fb908ca8c3fb0 + languageName: node + linkType: hard + "@smithy/querystring-builder@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/querystring-builder@npm:3.0.7" @@ -3365,6 +4032,16 @@ __metadata: languageName: node linkType: hard +"@smithy/querystring-parser@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/querystring-parser@npm:3.0.10" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/e57c15087246e6a50348d557b670ded987ed5d88d4279a0a4896828d2be9fb2949f6b6c8656e5be45282c25cfa2fe62fe7fd9bd159ac30177f5b99181a5f4b74 + languageName: node + linkType: hard + "@smithy/querystring-parser@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/querystring-parser@npm:3.0.7" @@ -3375,6 +4052,15 @@ __metadata: languageName: node linkType: hard +"@smithy/service-error-classification@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/service-error-classification@npm:3.0.10" + dependencies: + "@smithy/types": "npm:^3.7.1" + checksum: 10c0/9b9d5e0436d168f6a3290edb008292e2cc28ec7d2d9227858aff7c9c70d732336b71898eb0cb7fa76ea04c0180ec3afaf7930c92e881efd4b91023d7d8919044 + languageName: node + linkType: hard + "@smithy/service-error-classification@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/service-error-classification@npm:3.0.7" @@ -3384,6 +4070,16 @@ __metadata: languageName: node linkType: hard +"@smithy/shared-ini-file-loader@npm:^3.1.10, @smithy/shared-ini-file-loader@npm:^3.1.11": + version: 3.1.11 + resolution: "@smithy/shared-ini-file-loader@npm:3.1.11" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/7479713932f00a6b85380fa8012ad893bb61e7ea614976e0ab2898767ff7dc91bb1dd813a4ec72e4850d6b10296f11032cd5dd916970042be376c19d0d3954b6 + languageName: node + linkType: hard + "@smithy/shared-ini-file-loader@npm:^3.1.8": version: 3.1.8 resolution: "@smithy/shared-ini-file-loader@npm:3.1.8" @@ -3410,6 +4106,22 @@ __metadata: languageName: node linkType: hard +"@smithy/signature-v4@npm:^4.2.2": + version: 4.2.3 + resolution: "@smithy/signature-v4@npm:4.2.3" + dependencies: + "@smithy/is-array-buffer": "npm:^3.0.0" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-hex-encoding": "npm:^3.0.0" + "@smithy/util-middleware": "npm:^3.0.10" + "@smithy/util-uri-escape": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/7cecc9c73cb863e15c4517601a2a1e82b3728fbe174c533d807beb54f59f66792891c82955d874baa27640201d719b6ea63497b376e4c7cd09d5d52ea36fe3fc + languageName: node + linkType: hard + "@smithy/smithy-client@npm:^3.4.0": version: 3.4.0 resolution: "@smithy/smithy-client@npm:3.4.0" @@ -3424,6 +4136,21 @@ __metadata: languageName: node linkType: hard +"@smithy/smithy-client@npm:^3.4.4, @smithy/smithy-client@npm:^3.4.5": + version: 3.4.5 + resolution: "@smithy/smithy-client@npm:3.4.5" + dependencies: + "@smithy/core": "npm:^2.5.4" + "@smithy/middleware-endpoint": "npm:^3.2.4" + "@smithy/middleware-stack": "npm:^3.0.10" + "@smithy/protocol-http": "npm:^4.1.7" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-stream": "npm:^3.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/b9a56e20133d29ab2339d4d3b7b28601b7a98b899a7b0a5371c2c698c48e60c733fdad42fe1dec096c48a9de10d79de170f6eaa98a1bc1bd0c18a4b63c545e0d + languageName: node + linkType: hard + "@smithy/types@npm:^3.5.0": version: 3.5.0 resolution: "@smithy/types@npm:3.5.0" @@ -3433,6 +4160,26 @@ __metadata: languageName: node linkType: hard +"@smithy/types@npm:^3.7.1": + version: 3.7.1 + resolution: "@smithy/types@npm:3.7.1" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10c0/c82ad86087b6e0d2261f581a8cca1694a0af31458d7789ff5d8787973b4940a6d035082005dfc87857f266ee9cb512f7eb80535917e6dd6eb3d7d70c45d0f9aa + languageName: node + linkType: hard + +"@smithy/url-parser@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/url-parser@npm:3.0.10" + dependencies: + "@smithy/querystring-parser": "npm:^3.0.10" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/29c9d03ee86936ffb3bdcbb84ce14b7dacaadb2e61b5ed78ee91dfacb98e42048c70c718077347f0f39bce676168ba5fc1f1a8b19988f89f735c0b5e17cdc77a + languageName: node + linkType: hard + "@smithy/url-parser@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/url-parser@npm:3.0.7" @@ -3515,6 +4262,19 @@ __metadata: languageName: node linkType: hard +"@smithy/util-defaults-mode-browser@npm:^3.0.27": + version: 3.0.28 + resolution: "@smithy/util-defaults-mode-browser@npm:3.0.28" + dependencies: + "@smithy/property-provider": "npm:^3.1.10" + "@smithy/smithy-client": "npm:^3.4.5" + "@smithy/types": "npm:^3.7.1" + bowser: "npm:^2.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/bba460478f70ef25312d3e5408e0caa5feaf0b2af11aedcfd9e4719874884b507edd2503790d938e22fff5387f1dd63cd33c920dddf16cb3e6a6588575be5522 + languageName: node + linkType: hard + "@smithy/util-defaults-mode-node@npm:^3.0.23": version: 3.0.23 resolution: "@smithy/util-defaults-mode-node@npm:3.0.23" @@ -3530,6 +4290,21 @@ __metadata: languageName: node linkType: hard +"@smithy/util-defaults-mode-node@npm:^3.0.27": + version: 3.0.28 + resolution: "@smithy/util-defaults-mode-node@npm:3.0.28" + dependencies: + "@smithy/config-resolver": "npm:^3.0.12" + "@smithy/credential-provider-imds": "npm:^3.2.7" + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/property-provider": "npm:^3.1.10" + "@smithy/smithy-client": "npm:^3.4.5" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/6b49892d58d9c38e92e9b82ca7cdc2c9627f56fb3bc62ddef9bb5f197c38df1b7089c73c2256281888aba48a0ddd9319eb86a616af7ab40342f07aea1136dd47 + languageName: node + linkType: hard + "@smithy/util-endpoints@npm:^2.1.3": version: 2.1.3 resolution: "@smithy/util-endpoints@npm:2.1.3" @@ -3541,6 +4316,17 @@ __metadata: languageName: node linkType: hard +"@smithy/util-endpoints@npm:^2.1.6": + version: 2.1.6 + resolution: "@smithy/util-endpoints@npm:2.1.6" + dependencies: + "@smithy/node-config-provider": "npm:^3.1.11" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/a1cd8cc912fb67ee07e6095990f3b237b2e53f73e493b2aaa85af904c4ce73ce739a68e4d3330a37b8c96cd00b6845205b836ee4ced97cf622413a34b913adc2 + languageName: node + linkType: hard + "@smithy/util-hex-encoding@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/util-hex-encoding@npm:3.0.0" @@ -3550,6 +4336,16 @@ __metadata: languageName: node linkType: hard +"@smithy/util-middleware@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/util-middleware@npm:3.0.10" + dependencies: + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/01bbbd31044ab742985acac36aa61e240db16ed7dfa22b73779877eb5db0af14351883506fb34d2ee964598d72f4998d79409c271a62310647fb28faccd855a2 + languageName: node + linkType: hard + "@smithy/util-middleware@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/util-middleware@npm:3.0.7" @@ -3560,6 +4356,17 @@ __metadata: languageName: node linkType: hard +"@smithy/util-retry@npm:^3.0.10": + version: 3.0.10 + resolution: "@smithy/util-retry@npm:3.0.10" + dependencies: + "@smithy/service-error-classification": "npm:^3.0.10" + "@smithy/types": "npm:^3.7.1" + tslib: "npm:^2.6.2" + checksum: 10c0/ac1dcfd2e4ea1a4f99a42447b7fd8e4ea21589dfd87e9bc6a7bdf1d26e1f93ec71aa4cfde5e024b00d9b713b889f9db20a8d81b9e3ccdbe6f72bedb6269f01b8 + languageName: node + linkType: hard + "@smithy/util-retry@npm:^3.0.7": version: 3.0.7 resolution: "@smithy/util-retry@npm:3.0.7" @@ -3587,6 +4394,22 @@ __metadata: languageName: node linkType: hard +"@smithy/util-stream@npm:^3.3.1": + version: 3.3.1 + resolution: "@smithy/util-stream@npm:3.3.1" + dependencies: + "@smithy/fetch-http-handler": "npm:^4.1.1" + "@smithy/node-http-handler": "npm:^3.3.1" + "@smithy/types": "npm:^3.7.1" + "@smithy/util-base64": "npm:^3.0.0" + "@smithy/util-buffer-from": "npm:^3.0.0" + "@smithy/util-hex-encoding": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^3.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/dafaf4448e69cd65eda2bc7c43a48e945905808f635397e290b4e19cff2705ab444f1798829ca48b9a9efe4b7e569180eb6275ca42d04ce5abcf2dc9443f9c67 + languageName: node + linkType: hard + "@smithy/util-uri-escape@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/util-uri-escape@npm:3.0.0" @@ -7093,6 +7916,7 @@ __metadata: dependencies: "@ark-ui/react": "npm:^4.1.2" "@aws-sdk/client-bedrock-runtime": "npm:^3.670.0" + "@aws-sdk/client-transcribe": "npm:^3.699.0" "@babel/core": "npm:^7.24.5" "@dnd-kit/core": "npm:^6.0.8" "@dnd-kit/modifiers": "npm:^6.0.1" From 04ed47b64dc78b832dcf3be8bc4cc4f6986f1765 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Fri, 29 Nov 2024 12:21:47 +0100 Subject: [PATCH 04/30] Add helper function for polling --- src/components/Transcribe/helpers.ts | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/components/Transcribe/helpers.ts b/src/components/Transcribe/helpers.ts index 205b5e848a..75e517f54b 100644 --- a/src/components/Transcribe/helpers.ts +++ b/src/components/Transcribe/helpers.ts @@ -1,12 +1,3 @@ -/** - * Copyright (c) 2024-present, NDLA. - * - * This source code is licensed under the GPLv3 license found in the - * LICENSE file in the root directory of this source tree. - * - */ -import { GetTranscriptionJobCommand } from "@aws-sdk/client-transcribe"; - /** * Copyright (c) 2024-present, NDLA. * @@ -41,4 +32,21 @@ export const transcribe = async ({ fileUrl, maxSpeakers, mediaFormat, languageCo }, body: JSON.stringify(payload), }); + + return response.json(); +}; + +export const getTranscription = async (jobName: string) => { + const payload = { + jobName: jobName, + }; + + const response = await fetch("/get-transcription", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + return response.json(); }; From fdd9411464e99faff87040e1d74da7a58fa67145 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Fri, 29 Nov 2024 12:35:07 +0100 Subject: [PATCH 05/30] Remove default test value --- src/server/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/api.ts b/src/server/api.ts index d21bde64d7..f086406d75 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -212,7 +212,7 @@ router.post("/transcribe", async (req, res) => { Media: { MediaFileUri: req.body.mediaFileUri, }, - OutputBucketName: getEnvironmentVariabel("S3_TRANSCRIPTION_BUCKET_NAME") || "test.transcribe.ndla", + OutputBucketName: getEnvironmentVariabel("S3_TRANSCRIPTION_BUCKET_NAME"), OutputKey: req.body.outputFileName, Settings: { ShowSpeakerLabels: true, // Enable speaker identification From 814a7faa220efcfcdb0d50acd2ba99dd365258e2 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Sun, 1 Dec 2024 11:30:46 +0100 Subject: [PATCH 06/30] Move fetching of env variables to config --- src/config.ts | 10 ++++++++++ src/server/api.ts | 12 ++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/config.ts b/src/config.ts index b357e68d74..8e2ad72a45 100644 --- a/src/config.ts +++ b/src/config.ts @@ -158,6 +158,11 @@ export type ConfigType = { isVercel: boolean; defaultLanguage: LocaleType; runtimeType: RuntimeType; + aiModelID: string; + aiRegion: string; + aiSecretKey: string; + aiSecretID: string; + transcriptionBucketName: string; }; const getServerSideConfig = (): ConfigType => { @@ -202,6 +207,11 @@ const getServerSideConfig = (): ConfigType => { translateServiceUrl: getEnvironmentVariabel("NDKM_URL", getTranslateServiceUrl(ndlaEnvironment)), isVercel: getEnvironmentVariabel("IS_VERCEL", "false") === "true", runtimeType: getEnvironmentVariabel("NODE_ENV", "development") as "test" | "development" | "production", + aiModelID: getEnvironmentVariabel("NDLA_AI_MODEL_ID", ""), + aiRegion: getEnvironmentVariabel("NDLA_AI_MODEL_REGION", ""), + aiSecretKey: getEnvironmentVariabel("NDLA_AI_SECRET_KEY", ""), + aiSecretID: getEnvironmentVariabel("NDLA_AI_SECRET_ID", ""), + transcriptionBucketName: getEnvironmentVariabel("S3_TRANSCRIPTION_BUCKET_NAME", ""), }; }; diff --git a/src/server/api.ts b/src/server/api.ts index f086406d75..a91c636b3e 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -16,7 +16,7 @@ import { getToken, getBrightcoveToken, fetchAuth0UsersById, getEditors, getRespo import { OK, INTERNAL_SERVER_ERROR, NOT_ACCEPTABLE, FORBIDDEN } from "./httpCodes"; import errorLogger from "./logger"; import { translateDocument } from "./translate"; -import config, { getEnvironmentVariabel } from "../config"; +import config from "../config"; import { DRAFT_PUBLISH_SCOPE, DRAFT_WRITE_SCOPE } from "../constants"; import { NdlaError } from "../interfaces"; @@ -160,10 +160,10 @@ router.post("/translate", async (req, res) => { }); router.post("/invoke-model", async (req, res) => { - const modelId = getEnvironmentVariabel("NDLA_AI_MODEL_ID"); - const modelRegion = getEnvironmentVariabel("NDLA_AI_MODEL_REGION"); - const secretKey = getEnvironmentVariabel("NDLA_AI_SECRET_KEY", ""); - const secretId = getEnvironmentVariabel("NDLA_AI_SECRET_ID", ""); + const modelId = config.aiModelID; + const modelRegion = config.aiRegion; + const secretKey = config.aiSecretKey; + const secretId = config.aiSecretID; const client = new BedrockRuntimeClient({ region: modelRegion, //As of now this is the closest aws-region, with the service @@ -212,7 +212,7 @@ router.post("/transcribe", async (req, res) => { Media: { MediaFileUri: req.body.mediaFileUri, }, - OutputBucketName: getEnvironmentVariabel("S3_TRANSCRIPTION_BUCKET_NAME"), + OutputBucketName: config.transcriptionBucketName, OutputKey: req.body.outputFileName, Settings: { ShowSpeakerLabels: true, // Enable speaker identification From 903923065332335beca5bcaa080ef1fd1866b2bf Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Mon, 2 Dec 2024 07:07:44 +0100 Subject: [PATCH 07/30] load env in api file --- src/config.ts | 10 ---------- src/server/api.ts | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/config.ts b/src/config.ts index 8e2ad72a45..b357e68d74 100644 --- a/src/config.ts +++ b/src/config.ts @@ -158,11 +158,6 @@ export type ConfigType = { isVercel: boolean; defaultLanguage: LocaleType; runtimeType: RuntimeType; - aiModelID: string; - aiRegion: string; - aiSecretKey: string; - aiSecretID: string; - transcriptionBucketName: string; }; const getServerSideConfig = (): ConfigType => { @@ -207,11 +202,6 @@ const getServerSideConfig = (): ConfigType => { translateServiceUrl: getEnvironmentVariabel("NDKM_URL", getTranslateServiceUrl(ndlaEnvironment)), isVercel: getEnvironmentVariabel("IS_VERCEL", "false") === "true", runtimeType: getEnvironmentVariabel("NODE_ENV", "development") as "test" | "development" | "production", - aiModelID: getEnvironmentVariabel("NDLA_AI_MODEL_ID", ""), - aiRegion: getEnvironmentVariabel("NDLA_AI_MODEL_REGION", ""), - aiSecretKey: getEnvironmentVariabel("NDLA_AI_SECRET_KEY", ""), - aiSecretID: getEnvironmentVariabel("NDLA_AI_SECRET_ID", ""), - transcriptionBucketName: getEnvironmentVariabel("S3_TRANSCRIPTION_BUCKET_NAME", ""), }; }; diff --git a/src/server/api.ts b/src/server/api.ts index a91c636b3e..dbff681948 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -16,7 +16,7 @@ import { getToken, getBrightcoveToken, fetchAuth0UsersById, getEditors, getRespo import { OK, INTERNAL_SERVER_ERROR, NOT_ACCEPTABLE, FORBIDDEN } from "./httpCodes"; import errorLogger from "./logger"; import { translateDocument } from "./translate"; -import config from "../config"; +import config, { getEnvironmentVariabel } from "../config"; import { DRAFT_PUBLISH_SCOPE, DRAFT_WRITE_SCOPE } from "../constants"; import { NdlaError } from "../interfaces"; @@ -28,6 +28,12 @@ type NdlaUser = { permissions?: string[]; }; +const aiModelID = getEnvironmentVariabel("NDLA_AI_MODEL_ID", ""); +const aiRegion = getEnvironmentVariabel("NDLA_AI_MODEL_REGION", ""); +const aiSecretKey = getEnvironmentVariabel("NDLA_AI_SECRET_KEY", ""); +const aiSecretID = getEnvironmentVariabel("NDLA_AI_SECRET_ID", ""); +const transcriptionBucketName = getEnvironmentVariabel("S3_TRANSCRIPTION_BUCKET_NAME", ""); + // Temporal hack to send users to prod router.get("*splat", (req, res, next) => { if (!req.hostname.includes("ed.ff")) { @@ -160,14 +166,10 @@ router.post("/translate", async (req, res) => { }); router.post("/invoke-model", async (req, res) => { - const modelId = config.aiModelID; - const modelRegion = config.aiRegion; - const secretKey = config.aiSecretKey; - const secretId = config.aiSecretID; - + const modelId = aiModelID; const client = new BedrockRuntimeClient({ - region: modelRegion, //As of now this is the closest aws-region, with the service - credentials: { accessKeyId: secretId, secretAccessKey: secretKey }, + region: aiRegion, //As of now this is the closest aws-region, with the service + credentials: { accessKeyId: aiSecretID, secretAccessKey: aiSecretKey }, }); const payload = { @@ -212,7 +214,7 @@ router.post("/transcribe", async (req, res) => { Media: { MediaFileUri: req.body.mediaFileUri, }, - OutputBucketName: config.transcriptionBucketName, + OutputBucketName: transcriptionBucketName, OutputKey: req.body.outputFileName, Settings: { ShowSpeakerLabels: true, // Enable speaker identification From 2bdd97e3aa5ff17d949cb5d8e7f48392c56ac73d Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Mon, 2 Dec 2024 10:08:58 +0100 Subject: [PATCH 08/30] Remove fallback, add check if environment is set --- src/server/api.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/server/api.ts b/src/server/api.ts index dbff681948..a3b43ebf29 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -28,11 +28,11 @@ type NdlaUser = { permissions?: string[]; }; -const aiModelID = getEnvironmentVariabel("NDLA_AI_MODEL_ID", ""); -const aiRegion = getEnvironmentVariabel("NDLA_AI_MODEL_REGION", ""); -const aiSecretKey = getEnvironmentVariabel("NDLA_AI_SECRET_KEY", ""); -const aiSecretID = getEnvironmentVariabel("NDLA_AI_SECRET_ID", ""); -const transcriptionBucketName = getEnvironmentVariabel("S3_TRANSCRIPTION_BUCKET_NAME", ""); +const aiModelID = process.env.NDLA_AI_MODEL_ID; +const aiRegion = process.env.NDLA_AI_MODEL_REGION; +const aiSecretKey = process.env.NDLA_AI_SECRET_KEY; +const aiSecretID = process.env.NDLA_AI_SECRET_ID; +const transcriptionBucketName = process.env.S3_TRANSCRIPTION_BUCKET_NAME; // Temporal hack to send users to prod router.get("*splat", (req, res, next) => { @@ -167,6 +167,10 @@ router.post("/translate", async (req, res) => { router.post("/invoke-model", async (req, res) => { const modelId = aiModelID; + if (!aiRegion || !aiSecretID || !aiSecretKey || !modelId || !transcriptionBucketName) { + res.status(INTERNAL_SERVER_ERROR).send("Missing required environment variables"); + return; + } const client = new BedrockRuntimeClient({ region: aiRegion, //As of now this is the closest aws-region, with the service credentials: { accessKeyId: aiSecretID, secretAccessKey: aiSecretKey }, From 179b0f49c8ad998ab0ccb07a89b59cef460bce03 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Fri, 6 Dec 2024 10:42:09 +0100 Subject: [PATCH 09/30] make the polling a get --- src/server/api.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/server/api.ts b/src/server/api.ts index a3b43ebf29..9d30936f68 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -233,13 +233,11 @@ router.post("/transcribe", async (req, res) => { } }); -router.post("/get_transcription", async (req, res) => { - const jobName = req.body.jobName; - +router.get("/transcribe/:jobName", async (req, res) => { + const jobName = req.params.jobName; if (!jobName) { - res.status(404).send(""); + res.status(404).send("Job name is required"); } - const client = new TranscribeClient({ region: "eu-west-1", }); @@ -253,7 +251,6 @@ router.post("/get_transcription", async (req, res) => { return; } const jobStatus = response.TranscriptionJob.TranscriptionJobStatus; - if (jobStatus === "COMPLETED") { const transcriptUri = response.TranscriptionJob.Transcript?.TranscriptFileUri || ""; res.json({ jobName, status: "COMPLETED", transcriptUrl: transcriptUri }); From 835ceb27fdbcc1917370cb81c1655771e45a7044 Mon Sep 17 00:00:00 2001 From: Gaute Rauboti Date: Fri, 13 Dec 2024 07:38:44 +0100 Subject: [PATCH 10/30] Allow generation of alt text when creating image as well --- package.json | 1 + src/components/LLM/helpers.ts | 12 ++++- .../plugins/image/ImageEmbedForm.tsx | 13 ++++- .../ImageUploader/components/ImageContent.tsx | 47 ++++++++++++++++++- src/server/api.ts | 8 ++-- src/util/imageToBase64.ts | 28 ----------- yarn.lock | 20 +++++++- 7 files changed, 90 insertions(+), 39 deletions(-) delete mode 100644 src/util/imageToBase64.ts diff --git a/package.json b/package.json index 2b2e2efc34..453d425e51 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "@ndla/video-search": "^8.0.61-alpha.0", "@tanstack/react-query": "5.7.2", "auth0-js": "^9.22.1", + "buffer": "^6.0.3", "compression": "^1.7.4", "cross-fetch": "^3.1.5", "date-fns": "2.30.0", diff --git a/src/components/LLM/helpers.ts b/src/components/LLM/helpers.ts index 3103e44aa3..f3b13092c8 100644 --- a/src/components/LLM/helpers.ts +++ b/src/components/LLM/helpers.ts @@ -6,11 +6,16 @@ * */ +import { Buffer } from "buffer"; + export const claudeHaikuDefaults = { top_p: 0.7, top_k: 100, temperature: 0.9 }; interface modelProps { prompt: string; - image?: string; + image?: { + base64: string; + fileType: string; + }; max_tokens?: number; } @@ -24,6 +29,7 @@ export const invokeModel = async ({ prompt, image, max_tokens = 2000, ...rest }: if (image) { payload.image = image; } + const response = await fetch("/invoke-model", { method: "POST", headers: { @@ -50,3 +56,7 @@ export const getTextFromHTML = (html: string) => { const parseResponse = (response: string) => { return response.split("")[1].split("")[0].trim(); }; + +export const convertBufferToBase64 = (buffer: ArrayBuffer) => { + return Buffer.from(buffer).toString("base64"); +}; diff --git a/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx b/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx index c29789a440..858d883f97 100644 --- a/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx +++ b/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx @@ -28,7 +28,7 @@ import { import { styled } from "@ndla/styled-system/jsx"; import { IImageMetaInformationV3 } from "@ndla/types-backend/image-api"; import { ImageEmbedData } from "@ndla/types-embed"; -import { claudeHaikuDefaults, invokeModel } from "../../../../components/LLM/helpers"; +import { convertBufferToBase64, claudeHaikuDefaults, invokeModel } from "../../../../components/LLM/helpers"; import { InlineField } from "../../../../containers/FormikForm/InlineField"; import ImageEditor from "../../../../containers/ImageEditor/ImageEditor"; import { inlineContentToEditorValue, inlineContentToHTML } from "../../../../util/articleContentConverter"; @@ -171,9 +171,18 @@ const EmbedForm = ({ if (!image?.image.imageUrl) { return null; } + + const response = await fetch(image?.image.imageUrl); + const responseContentType = response.headers.get("Content-Type"); + const buffer = await response.arrayBuffer(); + const base64 = convertBufferToBase64(buffer); + const result = await invokeModel({ prompt: t("textGeneration.altText.prompt", { language: t(`languages.${language}`) }), - image: image?.image.imageUrl, + image: { + base64, + fileType: responseContentType ?? "", + }, max_tokens: 2000, ...claudeHaikuDefaults, }); diff --git a/src/containers/ImageUploader/components/ImageContent.tsx b/src/containers/ImageUploader/components/ImageContent.tsx index 0bfedafb0d..676df8d035 100644 --- a/src/containers/ImageUploader/components/ImageContent.tsx +++ b/src/containers/ImageUploader/components/ImageContent.tsx @@ -7,9 +7,10 @@ */ import { useFormikContext } from "formik"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { DeleteBinLine } from "@ndla/icons/action"; -import { UploadCloudLine } from "@ndla/icons/editor"; +import { BlogPost, UploadCloudLine } from "@ndla/icons/editor"; import { ImageMeta } from "@ndla/image-search"; import { Button, @@ -23,11 +24,13 @@ import { IconButton, FieldErrorMessage, FieldTextArea, + Spinner, } from "@ndla/primitives"; import { SafeLink } from "@ndla/safelink"; import { styled } from "@ndla/styled-system/jsx"; import { FormField } from "../../../components/FormField"; import { FormContent } from "../../../components/FormikForm"; +import { convertBufferToBase64, invokeModel, claudeHaikuDefaults } from "../../../components/LLM/helpers"; import { MAX_IMAGE_UPLOAD_SIZE } from "../../../constants"; import { TitleField } from "../../FormikForm"; import { ImageFormikType } from "../imageTransformers"; @@ -51,15 +54,44 @@ const ImageContentWrapper = styled("div", { }, }); +const StyledButton = styled(Button, { + base: { + alignSelf: "flex-start", + }, +}); + const ImageContent = () => { const { t } = useTranslation(); const formikContext = useFormikContext(); const { values, setFieldValue } = formikContext; + const [isLoading, setIsLoading] = useState(false); // We use the timestamp to avoid caching of the `imageFile` url in the browser const timestamp = new Date().getTime(); const imgSrc = values.filepath || `${values.imageFile}?width=800&ts=${timestamp}`; + const generateAltText = async () => { + setIsLoading(true); + if (!values.imageFile || typeof values.imageFile === "string") { + return null; + } + + const buffer = await values.imageFile.arrayBuffer(); + const base64 = convertBufferToBase64(buffer); + + const result = await invokeModel({ + prompt: t("textGeneration.altText.prompt"), + image: { + base64, + fileType: values.imageFile.type, + }, + max_tokens: 2000, + ...claudeHaikuDefaults, + }); + setIsLoading(false); + return result; + }; + return ( @@ -162,10 +194,21 @@ const ImageContent = () => { )} - {({ field, meta }) => ( + {({ field, meta, helpers }) => ( {t("form.image.alt.label")} + { + const text = await generateAltText(); + text && text.length > 0 && helpers.setValue(text); + }} + size="small" + title={t("textGeneration.altText.title")} + > + {t("textGeneration.altText.button")} + {isLoading ? : } + {meta.error} )} diff --git a/src/server/api.ts b/src/server/api.ts index 076ac5b8ea..46511161b9 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -16,10 +16,9 @@ import { getToken, getBrightcoveToken, fetchAuth0UsersById, getEditors, getRespo import { OK, INTERNAL_SERVER_ERROR, NOT_ACCEPTABLE, FORBIDDEN } from "./httpCodes"; import errorLogger from "./logger"; import { translateDocument } from "./translate"; -import config, { getEnvironmentVariabel } from "../config"; +import config from "../config"; import { DRAFT_PUBLISH_SCOPE, DRAFT_WRITE_SCOPE } from "../constants"; import { NdlaError } from "../interfaces"; -import { imageToBase64 } from "../util/imageToBase64"; const router = express.Router(); @@ -179,13 +178,12 @@ router.post("/invoke-model", async (req, res) => { const content = []; if (req.body.image) { - const { base64, filetype } = await imageToBase64(req.body.image); content.push({ type: "image", source: { type: "base64", - media_type: filetype, - data: base64, + media_type: req.body.image.fileType, + data: req.body.image.base64, }, }); } diff --git a/src/util/imageToBase64.ts b/src/util/imageToBase64.ts deleted file mode 100644 index 1d2671ed25..0000000000 --- a/src/util/imageToBase64.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2024-present, NDLA. - * - * This source code is licensed under the GPLv3 license found in the - * LICENSE file in the root directory of this source tree. - * - */ -export const imageToBase64 = async (url: string) => { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to fetch image: ${response.statusText}`); - } - - const contentType = response.headers.get("Content-Type"); - const buffer = await response.arrayBuffer(); - - if (!contentType) { - throw new Error("Failed to determine file type"); - } - - // Convert buffer to Base64 - const base64 = Buffer.from(buffer).toString("base64"); - - return { - base64, - filetype: contentType, - }; -}; diff --git a/yarn.lock b/yarn.lock index 81f6ab24d2..f316f28b36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6761,7 +6761,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.5.1": +"base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf @@ -6880,6 +6880,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10c0/2a905fbbcde73cc5d8bd18d1caa23715d5f83a5935867c2329f0ac06104204ba7947be098fe1317fbd8830e26090ff8e764f08cd14fefc977bb248c3487bcbd0 + languageName: node + linkType: hard + "bundle-n-require@npm:1.1.1": version: 1.1.1 resolution: "bundle-n-require@npm:1.1.1" @@ -7968,6 +7978,7 @@ __metadata: "@types/react-dom": "npm:^18.3.0" "@vitejs/plugin-react": "npm:^4.3.1" auth0-js: "npm:^9.22.1" + buffer: "npm:^6.0.3" compression: "npm:^1.7.4" concurrently: "npm:^8.2.2" cross-env: "npm:^7.0.3" @@ -10175,6 +10186,13 @@ __metadata: languageName: node linkType: hard +"ieee754@npm:^1.2.1": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + "ignore@npm:^5.2.0, ignore@npm:^5.2.4": version: 5.3.2 resolution: "ignore@npm:5.3.2" From c2d4ab8e254917134a4046aa173f8566590dc66e Mon Sep 17 00:00:00 2001 From: Gaute Rauboti Date: Fri, 13 Dec 2024 14:59:07 +0100 Subject: [PATCH 11/30] First UI updates to get transcription --- src/components/Transcribe/helpers.ts | 9 +- .../AudioUploader/components/AudioForm.tsx | 6 +- .../components/AudioManuscript.tsx | 89 ++++++++++++++++--- 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/components/Transcribe/helpers.ts b/src/components/Transcribe/helpers.ts index 75e517f54b..85d226ae13 100644 --- a/src/components/Transcribe/helpers.ts +++ b/src/components/Transcribe/helpers.ts @@ -37,16 +37,11 @@ export const transcribe = async ({ fileUrl, maxSpeakers, mediaFormat, languageCo }; export const getTranscription = async (jobName: string) => { - const payload = { - jobName: jobName, - }; - - const response = await fetch("/get-transcription", { - method: "POST", + const response = await fetch(`/transcribe/${jobName}`, { + method: "GET", headers: { "Content-Type": "application/json", }, - body: JSON.stringify(payload), }); return response.json(); }; diff --git a/src/containers/AudioUploader/components/AudioForm.tsx b/src/containers/AudioUploader/components/AudioForm.tsx index 8bfab3fefa..0daefe8521 100644 --- a/src/containers/AudioUploader/components/AudioForm.tsx +++ b/src/containers/AudioUploader/components/AudioForm.tsx @@ -222,7 +222,11 @@ const AudioForm = ({ title={t("podcastForm.fields.manuscript")} hasError={[].some((field) => field in errors)} > - + { +const AudioManuscript = ({ audioLanguage, audioUrl, audioType }: AudioManuscriptProps) => { const { t } = useTranslation(); + const [isLoading, setIsLoading] = useState(false); + + const generateText = async () => { + setIsLoading(true); + if (!audioUrl || !audioLanguage || !audioType) { + setIsLoading(false); + return; + } + + let language; + + if (audioLanguage === "nb" || audioLanguage === "nn") { + language = "no-NO"; + } else if (audioLanguage === "de") { + language = "de-DE"; + } else { + language = "en-US"; + } + + const jobName = await transcribe({ + fileUrl: audioUrl, + languageCode: language, + mediaFormat: audioType, + outputFileName: "transcription", + }); + // while (isLoading) { + // console.log("waiting for transcription"); + // setTimeout(async () => { + // const response = await getTranscription(jobName); + // if (response.status === "COMPLETED") { + // setIsLoading(false); + // return response.transcriptUrl; + // } else if (response.status === "FAILED") { + // setIsLoading(false); + // return "Could not transcribe audio"; + // } + // }, 10000); + // } + setIsLoading(false); + }; return ( - {({ field, form: { isSubmitting } }) => ( - field.onChange({ target: { value: val, name: field.name } })} - toolbarOptions={toolbarOptions} - toolbarAreaFilters={toolbarAreaFilters} - /> + {({ field, form: { isSubmitting }, helpers }) => ( + <> + field.onChange({ target: { value: val, name: field.name } })} + toolbarOptions={toolbarOptions} + toolbarAreaFilters={toolbarAreaFilters} + /> + {audioUrl && ( + + )} + )} ); }; -export default connect<{}, AudioFormikType>(AudioManuscript); +export default connect(AudioManuscript); From cfe42a2015486877603c9cade7b2f93e9f4b8541 Mon Sep 17 00:00:00 2001 From: Gaute Rauboti Date: Tue, 17 Dec 2024 08:04:14 +0100 Subject: [PATCH 12/30] Temporarily added s3 link for audio transcriptions --- src/config.ts | 11 +++++++++++ .../AudioUploader/components/AudioManuscript.tsx | 3 ++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index b357e68d74..dcf8e1f012 100644 --- a/src/config.ts +++ b/src/config.ts @@ -124,6 +124,15 @@ const usernamePasswordEnabled = (ndlaEnvironment: string) => { } }; +const getAudioS3Root = (ndlaEnvironment: string) => { + switch (ndlaEnvironment) { + case "prod": + return "s3://audio.2.ndla/"; + default: + return "s3://test.audio.2.ndla/"; + } +}; + export type ConfigType = { brightcoveAccountId: string | undefined; logEnvironment: string | undefined; @@ -158,6 +167,7 @@ export type ConfigType = { isVercel: boolean; defaultLanguage: LocaleType; runtimeType: RuntimeType; + s3AudioRoot: string; }; const getServerSideConfig = (): ConfigType => { @@ -202,6 +212,7 @@ const getServerSideConfig = (): ConfigType => { translateServiceUrl: getEnvironmentVariabel("NDKM_URL", getTranslateServiceUrl(ndlaEnvironment)), isVercel: getEnvironmentVariabel("IS_VERCEL", "false") === "true", runtimeType: getEnvironmentVariabel("NODE_ENV", "development") as "test" | "development" | "production", + s3AudioRoot: getAudioS3Root(ndlaEnvironment), }; }; diff --git a/src/containers/AudioUploader/components/AudioManuscript.tsx b/src/containers/AudioUploader/components/AudioManuscript.tsx index 198b881429..f8fcb72804 100644 --- a/src/containers/AudioUploader/components/AudioManuscript.tsx +++ b/src/containers/AudioUploader/components/AudioManuscript.tsx @@ -35,6 +35,7 @@ import { } from "../../../components/SlateEditor/plugins/toolbar/toolbarState"; import RichTextEditor from "../../../components/SlateEditor/RichTextEditor"; import { getTranscription, transcribe } from "../../../components/Transcribe/helpers"; +import config from "../../../config"; interface AudioManuscriptProps { audioLanguage?: string; @@ -109,7 +110,7 @@ const AudioManuscript = ({ audioLanguage, audioUrl, audioType }: AudioManuscript } const jobName = await transcribe({ - fileUrl: audioUrl, + fileUrl: config.s3AudioRoot + audioUrl.split("audio/files/")[1], languageCode: language, mediaFormat: audioType, outputFileName: "transcription", From 75d4b9b4e6ef7e0b80816dd36aa78da1269de030 Mon Sep 17 00:00:00 2001 From: Gaute Rauboti Date: Tue, 17 Dec 2024 09:40:28 +0100 Subject: [PATCH 13/30] Adding polling interval --- src/components/Transcribe/helpers.ts | 3 +- src/config.ts | 2 +- .../components/AudioManuscript.tsx | 30 +++++++------------ 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/components/Transcribe/helpers.ts b/src/components/Transcribe/helpers.ts index 85d226ae13..3db7a10af0 100644 --- a/src/components/Transcribe/helpers.ts +++ b/src/components/Transcribe/helpers.ts @@ -43,5 +43,6 @@ export const getTranscription = async (jobName: string) => { "Content-Type": "application/json", }, }); - return response.json(); + const result = await response.json(); + return result; }; diff --git a/src/config.ts b/src/config.ts index dcf8e1f012..749ca30f75 100644 --- a/src/config.ts +++ b/src/config.ts @@ -127,7 +127,7 @@ const usernamePasswordEnabled = (ndlaEnvironment: string) => { const getAudioS3Root = (ndlaEnvironment: string) => { switch (ndlaEnvironment) { case "prod": - return "s3://audio.2.ndla/"; + return "s3://prod.audio.2.ndla/"; default: return "s3://test.audio.2.ndla/"; } diff --git a/src/containers/AudioUploader/components/AudioManuscript.tsx b/src/containers/AudioUploader/components/AudioManuscript.tsx index f8fcb72804..cdfeab172b 100644 --- a/src/containers/AudioUploader/components/AudioManuscript.tsx +++ b/src/containers/AudioUploader/components/AudioManuscript.tsx @@ -109,25 +109,20 @@ const AudioManuscript = ({ audioLanguage, audioUrl, audioType }: AudioManuscript language = "en-US"; } - const jobName = await transcribe({ + const transcriptionResult = await transcribe({ fileUrl: config.s3AudioRoot + audioUrl.split("audio/files/")[1], languageCode: language, mediaFormat: audioType, outputFileName: "transcription", }); - // while (isLoading) { - // console.log("waiting for transcription"); - // setTimeout(async () => { - // const response = await getTranscription(jobName); - // if (response.status === "COMPLETED") { - // setIsLoading(false); - // return response.transcriptUrl; - // } else if (response.status === "FAILED") { - // setIsLoading(false); - // return "Could not transcribe audio"; - // } - // }, 10000); - // } + const pollingInterval = setInterval(async () => { + const response = await getTranscription(transcriptionResult.TranscriptionJob.TranscriptionJobName); + if (response.status === "COMPLETED") { + clearInterval(pollingInterval); + } else if (response.status === "FAILED") { + clearInterval(pollingInterval); + } + }, 10000); setIsLoading(false); }; @@ -146,12 +141,7 @@ const AudioManuscript = ({ audioLanguage, audioUrl, audioType }: AudioManuscript toolbarAreaFilters={toolbarAreaFilters} /> {audioUrl && ( - From 031cd2b76aea2410dae14c6c86ba60cad72e0485 Mon Sep 17 00:00:00 2001 From: Gaute Rauboti Date: Tue, 17 Dec 2024 10:02:16 +0100 Subject: [PATCH 14/30] Fix alt text for existing images --- .../ImageUploader/components/ImageContent.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/containers/ImageUploader/components/ImageContent.tsx b/src/containers/ImageUploader/components/ImageContent.tsx index 676df8d035..03f02f700b 100644 --- a/src/containers/ImageUploader/components/ImageContent.tsx +++ b/src/containers/ImageUploader/components/ImageContent.tsx @@ -71,19 +71,28 @@ const ImageContent = () => { const imgSrc = values.filepath || `${values.imageFile}?width=800&ts=${timestamp}`; const generateAltText = async () => { - setIsLoading(true); - if (!values.imageFile || typeof values.imageFile === "string") { + if (!values.imageFile) { return null; } - const buffer = await values.imageFile.arrayBuffer(); + let image; + if (typeof values.imageFile === "string") { + const result = await fetch(values.imageFile); + image = await result.blob(); + } else { + image = values.imageFile; + } + + setIsLoading(true); + + const buffer = await image.arrayBuffer(); const base64 = convertBufferToBase64(buffer); const result = await invokeModel({ prompt: t("textGeneration.altText.prompt"), image: { base64, - fileType: values.imageFile.type, + fileType: image.type, }, max_tokens: 2000, ...claudeHaikuDefaults, From 1b5c955d830cc410150363268190d05bd96b40cc Mon Sep 17 00:00:00 2001 From: Gaute Rauboti Date: Tue, 17 Dec 2024 10:37:18 +0100 Subject: [PATCH 15/30] Enabled for podcasts --- src/containers/Podcast/components/PodcastForm.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/containers/Podcast/components/PodcastForm.tsx b/src/containers/Podcast/components/PodcastForm.tsx index 370f085863..b813e747a5 100644 --- a/src/containers/Podcast/components/PodcastForm.tsx +++ b/src/containers/Podcast/components/PodcastForm.tsx @@ -262,7 +262,11 @@ const PodcastForm = ({ title={t("podcastForm.fields.manuscript")} hasError={[].some((field) => field in errors)} > - + Date: Tue, 17 Dec 2024 11:34:12 +0100 Subject: [PATCH 16/30] Return text from endpoint to field + fix button text --- .../AudioUploader/components/AudioManuscript.tsx | 14 +++++++++++--- src/phrases/phrases-en.ts | 3 +++ src/phrases/phrases-nb.ts | 3 +++ src/phrases/phrases-nn.ts | 3 +++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/containers/AudioUploader/components/AudioManuscript.tsx b/src/containers/AudioUploader/components/AudioManuscript.tsx index cdfeab172b..f2134f72a5 100644 --- a/src/containers/AudioUploader/components/AudioManuscript.tsx +++ b/src/containers/AudioUploader/components/AudioManuscript.tsx @@ -96,7 +96,7 @@ const AudioManuscript = ({ audioLanguage, audioUrl, audioType }: AudioManuscript setIsLoading(true); if (!audioUrl || !audioLanguage || !audioType) { setIsLoading(false); - return; + return null; } let language; @@ -119,8 +119,10 @@ const AudioManuscript = ({ audioLanguage, audioUrl, audioType }: AudioManuscript const response = await getTranscription(transcriptionResult.TranscriptionJob.TranscriptionJobName); if (response.status === "COMPLETED") { clearInterval(pollingInterval); + return response.transcription; } else if (response.status === "FAILED") { clearInterval(pollingInterval); + return null; } }, 10000); setIsLoading(false); @@ -141,8 +143,14 @@ const AudioManuscript = ({ audioLanguage, audioUrl, audioType }: AudioManuscript toolbarAreaFilters={toolbarAreaFilters} /> {audioUrl && ( - )} diff --git a/src/phrases/phrases-en.ts b/src/phrases/phrases-en.ts index 10102515c2..da631e64df 100644 --- a/src/phrases/phrases-en.ts +++ b/src/phrases/phrases-en.ts @@ -2273,6 +2273,9 @@ const phrases = { The response must be given with reflections as to why you have chosen to include the selected points first. Then, the reflection questions must be given as bulletpoints in an tag. `, }, + transcription: { + button: "Generate", + }, }, }; diff --git a/src/phrases/phrases-nb.ts b/src/phrases/phrases-nb.ts index d438d42a69..eb4aad2cee 100644 --- a/src/phrases/phrases-nb.ts +++ b/src/phrases/phrases-nb.ts @@ -2271,6 +2271,9 @@ const phrases = { Svaret skal leveres med refleksjoner på hvorfor du har valgt å inkludere de punktene du har valgt først. Deretter skal refleksjonsspørsmålene gis som punktliste i en tag. `, }, + transcription: { + button: "Generer", + }, }, }; diff --git a/src/phrases/phrases-nn.ts b/src/phrases/phrases-nn.ts index ab7b3f8589..6f81081f20 100644 --- a/src/phrases/phrases-nn.ts +++ b/src/phrases/phrases-nn.ts @@ -2273,6 +2273,9 @@ const phrases = { Svaret skal leverast med refleksjonar på kvifor du har valt å inkludere dei punktane du har valt først. Deretter skal refleksjonsspørmåla gjes som punktliste i ein tag. `, }, + transcription: { + button: "Generer", + }, }, }; From 8046b54e65682403c7f1030c7cc546efba2a0a3b Mon Sep 17 00:00:00 2001 From: Gaute Rauboti Date: Tue, 17 Dec 2024 14:02:15 +0100 Subject: [PATCH 17/30] Fix linting --- .../SlateEditor/plugins/image/ImageEmbedForm.tsx | 4 +++- .../AudioUploader/components/AudioManuscript.tsx | 8 +++++--- src/containers/ImageUploader/components/ImageContent.tsx | 4 +++- src/server/api.ts | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx b/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx index efda72b61f..83210726d2 100644 --- a/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx +++ b/src/components/SlateEditor/plugins/image/ImageEmbedForm.tsx @@ -224,7 +224,9 @@ const EmbedForm = ({ { const text = await generateAltText(); - text && text.length > 0 && helpers.setValue(text); + if (text && text.length > 0) { + helpers.setValue(text); + } }} size="small" title={t("textGeneration.altText.title")} diff --git a/src/containers/AudioUploader/components/AudioManuscript.tsx b/src/containers/AudioUploader/components/AudioManuscript.tsx index 4ab2ed1122..9411499da0 100644 --- a/src/containers/AudioUploader/components/AudioManuscript.tsx +++ b/src/containers/AudioUploader/components/AudioManuscript.tsx @@ -9,8 +9,8 @@ import { connect, useFormikContext } from "formik"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Button, FieldErrorMessage, FieldRoot, Spinner } from "@ndla/primitives"; import { FileListLine } from "@ndla/icons"; +import { Button, FieldErrorMessage, FieldRoot, Spinner } from "@ndla/primitives"; import { AudioFormikType } from "./AudioForm"; import { ContentEditableFieldLabel } from "../../../components/Form/ContentEditableFieldLabel"; import { FieldWarning } from "../../../components/Form/FieldWarning"; @@ -135,11 +135,13 @@ const AudioManuscript = ({ audioLanguage, audioUrl, audioType }: AudioManuscript /> {meta.error} - {audioUrl && ( + {!!audioUrl && ( - )} + ) : null} )} diff --git a/src/modules/audio/audioApi.ts b/src/modules/audio/audioApi.ts index 0839263693..f01e41f63f 100644 --- a/src/modules/audio/audioApi.ts +++ b/src/modules/audio/audioApi.ts @@ -15,13 +15,18 @@ import { ITagsSearchResultDTO, ISeriesSearchParamsDTO, ISearchParamsDTO, + ITranscriptionResultDTO, } from "@ndla/types-backend/audio-api"; import { StringSort } from "../../containers/SearchPage/components/form/SearchForm"; import { apiResourceUrl, fetchAuthorized, resolveJsonOrRejectWithError } from "../../util/apiHelpers"; -import { resolveJsonOrVoidOrRejectWithError } from "../../util/resolveJsonOrRejectWithError"; +import { + resolveJsonOrVoidOrRejectWithError, + resolveVoidOrRejectWithError, +} from "../../util/resolveJsonOrRejectWithError"; const baseUrl = apiResourceUrl("/audio-api/v1/audio"); const seriesBaseUrl = apiResourceUrl("/audio-api/v1/series"); +const transcribeUrl = apiResourceUrl("/audio-api/v1/transcription"); export const postAudio = (formData: FormData): Promise => fetchAuthorized(`${baseUrl}`, { @@ -90,3 +95,20 @@ export const postSearchSeries = async ( const response = await fetchAuthorized(`${seriesBaseUrl}/search/`, { method: "POST", body: JSON.stringify(body) }); return resolveJsonOrRejectWithError(response); }; + +export const postAudioTranscription = async (audioName: string, audioId: number, language: string): Promise => { + const response = await fetchAuthorized(`${transcribeUrl}/audio/${audioName}/${audioId}/${language}`, { + method: "POST", + }); + return resolveVoidOrRejectWithError(response); +}; + +export const fetchAudioTranscription = ( + audioName: string, + audioId: number, + language: string, +): Promise => { + return fetchAuthorized(`${transcribeUrl}/audio/${audioName}/${audioId}/${language}`, { method: "GET" }).then((r) => + resolveJsonOrRejectWithError(r), + ); +}; diff --git a/src/modules/audio/audioQueries.ts b/src/modules/audio/audioQueries.ts index c647e10837..56402a6879 100644 --- a/src/modules/audio/audioQueries.ts +++ b/src/modules/audio/audioQueries.ts @@ -15,8 +15,16 @@ import { ISeriesSearchParamsDTO, ISearchParamsDTO as IAudioSearchParams, ITagsSearchResultDTO, + ITranscriptionResultDTO, } from "@ndla/types-backend/audio-api"; -import { fetchAudio, fetchSearchTags, fetchSeries, postSearchAudio, postSearchSeries } from "./audioApi"; +import { + fetchAudio, + fetchAudioTranscription, + fetchSearchTags, + fetchSeries, + postSearchAudio, + postSearchSeries, +} from "./audioApi"; import { StringSort } from "../../containers/SearchPage/components/form/SearchForm"; import { AUDIO, PODCAST_SERIES, SEARCH_AUDIO, AUDIO_SEARCH_TAGS, SEARCH_SERIES } from "../../queryKeys"; @@ -86,3 +94,20 @@ export const useAudioSearchTags = (params: UseSearchTags, options?: Partial>, +) => { + return useQuery({ + queryKey: ["audioTranscription", params], + queryFn: () => fetchAudioTranscription(params.audioName, params.audioId, params.language), + ...options, + }); +}; From 4e5133217d3a470fb01adc9fd9642e7db6bb65b8 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Wed, 18 Dec 2024 13:26:59 +0100 Subject: [PATCH 19/30] Fix polling for audio --- src/components/SlateEditor/RichTextEditor.tsx | 6 +- .../components/AudioManuscript.tsx | 147 +++++++++--------- src/modules/audio/audioQueries.ts | 2 + 3 files changed, 79 insertions(+), 76 deletions(-) diff --git a/src/components/SlateEditor/RichTextEditor.tsx b/src/components/SlateEditor/RichTextEditor.tsx index 44d17e28f9..9bb08b4399 100644 --- a/src/components/SlateEditor/RichTextEditor.tsx +++ b/src/components/SlateEditor/RichTextEditor.tsx @@ -140,7 +140,11 @@ const RichTextEditor = ({ useEffect(() => { // When form is submitted or form content has been revert to a previous version, the editor has to be reinitialized. - if ((!submitted && prevSubmitted.current) || status === "revertVersion") { + if ( + (!submitted && prevSubmitted.current) || + status.status === "revertVersion" || + status.status === "acceptGenerated" + ) { if (isFirstNormalize) { return; } diff --git a/src/containers/AudioUploader/components/AudioManuscript.tsx b/src/containers/AudioUploader/components/AudioManuscript.tsx index 8f46e9ed57..2a19bee6d7 100644 --- a/src/containers/AudioUploader/components/AudioManuscript.tsx +++ b/src/containers/AudioUploader/components/AudioManuscript.tsx @@ -6,9 +6,11 @@ * */ -import { connect, useFormikContext } from "formik"; -import { useState } from "react"; +import { connect, FieldHelperProps, useField, useFormikContext } from "formik"; +import { update } from "lodash"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { Descendant } from "slate"; import { FileListLine } from "@ndla/icons"; import { Button, FieldErrorMessage, FieldRoot, Spinner } from "@ndla/primitives"; import { AudioFormikType } from "./AudioForm"; @@ -34,10 +36,10 @@ import { createToolbarDefaultValues, } from "../../../components/SlateEditor/plugins/toolbar/toolbarState"; import RichTextEditor from "../../../components/SlateEditor/RichTextEditor"; -import { getTranscription, transcribe } from "../../../components/Transcribe/helpers"; -import config from "../../../config"; import { postAudioTranscription } from "../../../modules/audio/audioApi"; import { useAudioTranscription } from "../../../modules/audio/audioQueries"; +import { inlineContentToEditorValue } from "../../../util/articleContentConverter"; +import { ArticleFormType } from "../../FormikForm/articleFormHooks"; interface AudioManuscriptProps { audioName?: string; @@ -81,97 +83,92 @@ const plugins = manuscriptPlugins.concat(manuscriptRenderers); const AudioManuscript = ({ audioId, audioLanguage, audioUrl, audioType }: AudioManuscriptProps) => { const { t } = useTranslation(); + const { setStatus } = useFormikContext(); const { isSubmitting } = useFormikContext(); const [isLoading, setIsLoading] = useState(false); + const getLanguage = (audioLanguage: string) => { + const languageMap: { [key: string]: string } = { + nb: "no-NO", + nn: "no-NO", + de: "de-DE", + }; + + return languageMap[audioLanguage] || "en-US"; + }; + + const language = getLanguage(audioLanguage!); + const audioName = audioUrl?.split("audio/files/")[1]; const { data: transcribeData } = useAudioTranscription( { - audioName: audioUrl?.split("audio/files/")[1]!, + audioName: audioName!, audioId: audioId!, - language: "no-NO", + language: language, }, { enabled: isLoading, }, ); - const generateText = async () => { + + const [_field, _meta, helpers] = useField("manuscript"); + + const startJob = () => { if (!audioUrl || !audioLanguage || !audioType || !audioId) { return null; } setIsLoading(true); - let language; - - if (audioLanguage === "nb" || audioLanguage === "nn") { - language = "no-NO"; - } else if (audioLanguage === "de") { - language = "de-DE"; - } else { - language = "en-US"; - } - await postAudioTranscription(audioUrl?.split("audio/files/")[1], audioId, language); - const pollingInterval = setInterval(async () => { - if (transcribeData?.status === "COMPLETED") { - clearInterval(pollingInterval); - return transcribeData.transcription; - } else if (transcribeData?.status === "FAILED") { - clearInterval(pollingInterval); - return null; - } - }, 10000); - /* const transcriptionResult = await transcribe({ - fileUrl: config.s3AudioRoot + audioUrl.split("audio/files/")[1], - languageCode: language, - mediaFormat: audioType, - outputFileName: "transcription", - }); - const pollingInterval = setInterval(async () => { - const response = await getTranscription(transcriptionResult.TranscriptionJob.TranscriptionJobName); - if (response.status === "COMPLETED") { - clearInterval(pollingInterval); - return response.transcription; - } else if (response.status === "FAILED") { - clearInterval(pollingInterval); - return null; - } - }, 10000);*/ - setIsLoading(false); + + postAudioTranscription(audioUrl?.split("audio/files/")[1], audioId, language); + }; + + const getTranscriptText = (text: string) => { + const json = JSON.parse(text); + return json.results.transcripts[0].transcript; }; + useEffect(() => { + if (transcribeData?.status === "COMPLETED" && isLoading) { + setIsLoading(false); + const transcriptText = getTranscriptText(transcribeData?.transcription ?? ""); + const editorContent = inlineContentToEditorValue(transcriptText, true); + helpers.setValue(editorContent, true); + setStatus({ status: "acceptGenerated" }); + } else if (transcribeData?.status === "FAILED" && isLoading) { + setIsLoading(false); + } + }, [setStatus, helpers, isLoading, transcribeData]); + return ( - {({ field, meta, helpers }) => ( - - - {t("podcastForm.fields.manuscript")} - - - {meta.error} - - {!!audioUrl && ( - - )} - - )} + toolbarOptions={toolbarOptions} + toolbarAreaFilters={toolbarAreaFilters} + /> + {meta.error} + + {!!audioUrl && ( + + )} + + ); + }} ); }; diff --git a/src/modules/audio/audioQueries.ts b/src/modules/audio/audioQueries.ts index 56402a6879..059a8f66ed 100644 --- a/src/modules/audio/audioQueries.ts +++ b/src/modules/audio/audioQueries.ts @@ -108,6 +108,8 @@ export const useAudioTranscription = ( return useQuery({ queryKey: ["audioTranscription", params], queryFn: () => fetchAudioTranscription(params.audioName, params.audioId, params.language), + refetchInterval: 1000, + refetchIntervalInBackground: true, ...options, }); }; From e6780f8bde78fb795d63e3a83b79c8e5245499f4 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Wed, 18 Dec 2024 13:46:26 +0100 Subject: [PATCH 20/30] lint --- src/containers/AudioUploader/components/AudioManuscript.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/containers/AudioUploader/components/AudioManuscript.tsx b/src/containers/AudioUploader/components/AudioManuscript.tsx index 2a19bee6d7..b8f7f9d37a 100644 --- a/src/containers/AudioUploader/components/AudioManuscript.tsx +++ b/src/containers/AudioUploader/components/AudioManuscript.tsx @@ -6,11 +6,9 @@ * */ -import { connect, FieldHelperProps, useField, useFormikContext } from "formik"; -import { update } from "lodash"; +import { connect, useField, useFormikContext } from "formik"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Descendant } from "slate"; import { FileListLine } from "@ndla/icons"; import { Button, FieldErrorMessage, FieldRoot, Spinner } from "@ndla/primitives"; import { AudioFormikType } from "./AudioForm"; From 23c75e83f36efe80c41402c61ea4e80abbdaea15 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Wed, 18 Dec 2024 15:03:05 +0100 Subject: [PATCH 21/30] update to poll only on ID --- .../AudioUploader/components/AudioManuscript.tsx | 2 -- src/modules/audio/audioApi.ts | 8 ++------ src/modules/audio/audioQueries.ts | 3 +-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/containers/AudioUploader/components/AudioManuscript.tsx b/src/containers/AudioUploader/components/AudioManuscript.tsx index b8f7f9d37a..fd953c7430 100644 --- a/src/containers/AudioUploader/components/AudioManuscript.tsx +++ b/src/containers/AudioUploader/components/AudioManuscript.tsx @@ -95,10 +95,8 @@ const AudioManuscript = ({ audioId, audioLanguage, audioUrl, audioType }: AudioM }; const language = getLanguage(audioLanguage!); - const audioName = audioUrl?.split("audio/files/")[1]; const { data: transcribeData } = useAudioTranscription( { - audioName: audioName!, audioId: audioId!, language: language, }, diff --git a/src/modules/audio/audioApi.ts b/src/modules/audio/audioApi.ts index f01e41f63f..c9a6a30a0c 100644 --- a/src/modules/audio/audioApi.ts +++ b/src/modules/audio/audioApi.ts @@ -103,12 +103,8 @@ export const postAudioTranscription = async (audioName: string, audioId: number, return resolveVoidOrRejectWithError(response); }; -export const fetchAudioTranscription = ( - audioName: string, - audioId: number, - language: string, -): Promise => { - return fetchAuthorized(`${transcribeUrl}/audio/${audioName}/${audioId}/${language}`, { method: "GET" }).then((r) => +export const fetchAudioTranscription = (audioId: number, language: string): Promise => { + return fetchAuthorized(`${transcribeUrl}/audio/${audioId}/${language}`, { method: "GET" }).then((r) => resolveJsonOrRejectWithError(r), ); }; diff --git a/src/modules/audio/audioQueries.ts b/src/modules/audio/audioQueries.ts index 059a8f66ed..1bae9e1517 100644 --- a/src/modules/audio/audioQueries.ts +++ b/src/modules/audio/audioQueries.ts @@ -96,7 +96,6 @@ export const useAudioSearchTags = (params: UseSearchTags, options?: Partial { return useQuery({ queryKey: ["audioTranscription", params], - queryFn: () => fetchAudioTranscription(params.audioName, params.audioId, params.language), + queryFn: () => fetchAudioTranscription(params.audioId, params.language), refetchInterval: 1000, refetchIntervalInBackground: true, ...options, From 7bd47aa1972b786323c3b0ede56e6dcfcaf81ac0 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Sat, 21 Dec 2024 14:29:55 +0100 Subject: [PATCH 22/30] check if job has been performed --- .../components/AudioManuscript.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/containers/AudioUploader/components/AudioManuscript.tsx b/src/containers/AudioUploader/components/AudioManuscript.tsx index fd953c7430..ab4c95534a 100644 --- a/src/containers/AudioUploader/components/AudioManuscript.tsx +++ b/src/containers/AudioUploader/components/AudioManuscript.tsx @@ -34,7 +34,7 @@ import { createToolbarDefaultValues, } from "../../../components/SlateEditor/plugins/toolbar/toolbarState"; import RichTextEditor from "../../../components/SlateEditor/RichTextEditor"; -import { postAudioTranscription } from "../../../modules/audio/audioApi"; +import { fetchAudioTranscription, postAudioTranscription } from "../../../modules/audio/audioApi"; import { useAudioTranscription } from "../../../modules/audio/audioQueries"; import { inlineContentToEditorValue } from "../../../util/articleContentConverter"; import { ArticleFormType } from "../../FormikForm/articleFormHooks"; @@ -107,14 +107,21 @@ const AudioManuscript = ({ audioId, audioLanguage, audioUrl, audioType }: AudioM const [_field, _meta, helpers] = useField("manuscript"); - const startJob = () => { + const checkJobStatus = async (): Promise => { + const response = await fetchAudioTranscription(audioId!, language); + return response.status !== "COMPLETE"; + }; + + const startJob = async () => { if (!audioUrl || !audioLanguage || !audioType || !audioId) { return null; } - - setIsLoading(true); - - postAudioTranscription(audioUrl?.split("audio/files/")[1], audioId, language); + const shouldPost = await checkJobStatus(); + if (shouldPost) { + postAudioTranscription(audioUrl?.split("audio/files/")[1], audioId, language).then((_) => { + setIsLoading(true); + }); + } }; const getTranscriptText = (text: string) => { From d68e4d5c8ae9040fee526864e9b506f7c4497668 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Mon, 13 Jan 2025 12:38:46 +0100 Subject: [PATCH 23/30] Fix transcription calls --- .../components/AudioManuscript.tsx | 21 ++++++++++++++++--- src/modules/audio/audioApi.ts | 9 +++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/containers/AudioUploader/components/AudioManuscript.tsx b/src/containers/AudioUploader/components/AudioManuscript.tsx index ab4c95534a..34be25d206 100644 --- a/src/containers/AudioUploader/components/AudioManuscript.tsx +++ b/src/containers/AudioUploader/components/AudioManuscript.tsx @@ -107,6 +107,11 @@ const AudioManuscript = ({ audioId, audioLanguage, audioUrl, audioType }: AudioM const [_field, _meta, helpers] = useField("manuscript"); + const fetchTranscription = async () => { + const response = await fetchAudioTranscription(audioId!, language); + return await JSON.parse(response.transcription!); + }; + const checkJobStatus = async (): Promise => { const response = await fetchAudioTranscription(audioId!, language); return response.status !== "COMPLETE"; @@ -118,9 +123,19 @@ const AudioManuscript = ({ audioId, audioLanguage, audioUrl, audioType }: AudioM } const shouldPost = await checkJobStatus(); if (shouldPost) { - postAudioTranscription(audioUrl?.split("audio/files/")[1], audioId, language).then((_) => { - setIsLoading(true); - }); + postAudioTranscription(audioUrl?.split("audio/files/")[1], audioId, language) + .then((_) => { + setIsLoading(true); + }) + .catch(async (err) => { + if (err.status === 400 && err.json.code === "JOB_ALREADY_FOUND") { + const fetchedTranscribeData = await fetchTranscription(); + const editedContent = fetchedTranscribeData.results.transcripts[0].transcript; + const editorContent = inlineContentToEditorValue(editedContent, true); + helpers.setValue(editorContent, true); + setStatus({ status: "acceptGenerated" }); + } + }); } }; diff --git a/src/modules/audio/audioApi.ts b/src/modules/audio/audioApi.ts index c9a6a30a0c..21c15cfa81 100644 --- a/src/modules/audio/audioApi.ts +++ b/src/modules/audio/audioApi.ts @@ -104,7 +104,10 @@ export const postAudioTranscription = async (audioName: string, audioId: number, }; export const fetchAudioTranscription = (audioId: number, language: string): Promise => { - return fetchAuthorized(`${transcribeUrl}/audio/${audioId}/${language}`, { method: "GET" }).then((r) => - resolveJsonOrRejectWithError(r), - ); + return fetchAuthorized(`${transcribeUrl}/audio/${audioId}/${language}`, { method: "GET" }) + .then((r) => resolveJsonOrRejectWithError(r)) + .catch((e) => { + console.error("Error fetching transcription", e); + return { status: "error" } as ITranscriptionResultDTO; + }); }; From 5c90433d7c186047c636607ee22c1304ad73f933 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Mon, 13 Jan 2025 13:06:16 +0100 Subject: [PATCH 24/30] Remove console error --- src/modules/audio/audioApi.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/audio/audioApi.ts b/src/modules/audio/audioApi.ts index 21c15cfa81..887b4018d3 100644 --- a/src/modules/audio/audioApi.ts +++ b/src/modules/audio/audioApi.ts @@ -106,8 +106,7 @@ export const postAudioTranscription = async (audioName: string, audioId: number, export const fetchAudioTranscription = (audioId: number, language: string): Promise => { return fetchAuthorized(`${transcribeUrl}/audio/${audioId}/${language}`, { method: "GET" }) .then((r) => resolveJsonOrRejectWithError(r)) - .catch((e) => { - console.error("Error fetching transcription", e); + .catch(() => { return { status: "error" } as ITranscriptionResultDTO; }); }; From 74e77af7e314bbb03da8c8bf7d44edd551a5f9fe Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Mon, 13 Jan 2025 14:40:52 +0100 Subject: [PATCH 25/30] Use correct env variable --- src/server/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/api.ts b/src/server/api.ts index a000248d8e..70ee7eb588 100644 --- a/src/server/api.ts +++ b/src/server/api.ts @@ -32,7 +32,7 @@ const aiModelID = process.env.NDLA_AI_MODEL_ID; const aiRegion = process.env.NDLA_AI_MODEL_REGION; const aiSecretKey = process.env.NDLA_AI_SECRET_KEY; const aiSecretID = process.env.NDLA_AI_SECRET_ID; -const transcriptionBucketName = process.env.S3_TRANSCRIPTION_BUCKET_NAME; +const transcriptionBucketName = process.env.TRANSCRIBE_FILE_S3_BUCKET; // Temporal hack to send users to prod router.get("*splat", (req, res, next) => { From c1610b54d9fb6111d4fc070dee4642f5f594fb64 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Wed, 15 Jan 2025 11:57:34 +0100 Subject: [PATCH 26/30] Bump backend --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 1ce03f5e4c..96f95eba57 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@babel/core": "^7.26.0", "@ndla/preset-panda": "^0.0.48", "@ndla/scripts": "^2.1.3", - "@ndla/types-backend": "^1.0.1", + "@ndla/types-backend": "^1.0.15", "@ndla/types-embed": "^5.0.6-alpha.0", "@ndla/types-taxonomy": "^1.0.30", "@pandacss/dev": "^0.48.0", diff --git a/yarn.lock b/yarn.lock index 3d8da46b30..838d1136e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2886,10 +2886,10 @@ __metadata: languageName: node linkType: hard -"@ndla/types-backend@npm:^1.0.1": - version: 1.0.1 - resolution: "@ndla/types-backend@npm:1.0.1" - checksum: 10c0/417fd69c5fc526f4b90371b0d00162909c2cec496e4228230990839c10d2c5a0a0caf562cb4e7e44c8bf66b4583b826d87f3aababaed6e3a9e727ba8385f0e1b +"@ndla/types-backend@npm:^1.0.15": + version: 1.0.15 + resolution: "@ndla/types-backend@npm:1.0.15" + checksum: 10c0/95d18e3eed0d034a7579afcbdf58a9639d8557dc67e485f79473b8aaaa5b3f24269fb5766aec0bbf192da7b2f466d45bf52064fa3f6659dde5c29e8b503749b1 languageName: node linkType: hard @@ -7715,7 +7715,7 @@ __metadata: "@ndla/safelink": "npm:^7.0.66-alpha.0" "@ndla/scripts": "npm:^2.1.3" "@ndla/styled-system": "npm:^0.0.29" - "@ndla/types-backend": "npm:^1.0.1" + "@ndla/types-backend": "npm:^1.0.15" "@ndla/types-embed": "npm:^5.0.6-alpha.0" "@ndla/types-taxonomy": "npm:^1.0.30" "@ndla/ui": "npm:^56.0.81-alpha.0" From a23c99c2fd9eae2991ef7ed58fc6e038c6d0642c Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Wed, 15 Jan 2025 12:51:55 +0100 Subject: [PATCH 27/30] Handle undefined status --- src/components/SlateEditor/RichTextEditor.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SlateEditor/RichTextEditor.tsx b/src/components/SlateEditor/RichTextEditor.tsx index 9bb08b4399..a091f340d7 100644 --- a/src/components/SlateEditor/RichTextEditor.tsx +++ b/src/components/SlateEditor/RichTextEditor.tsx @@ -142,8 +142,8 @@ const RichTextEditor = ({ // When form is submitted or form content has been revert to a previous version, the editor has to be reinitialized. if ( (!submitted && prevSubmitted.current) || - status.status === "revertVersion" || - status.status === "acceptGenerated" + status?.status === "revertVersion" || + status?.status === "acceptGenerated" ) { if (isFirstNormalize) { return; From 7f9d826302177884503bde427d3b5ec60a94dd5d Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Wed, 15 Jan 2025 15:43:37 +0100 Subject: [PATCH 28/30] Add vercel --- package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index 96f95eba57..b3122252ad 100644 --- a/package.json +++ b/package.json @@ -141,6 +141,7 @@ "slate-hyperscript": "^0.100.0", "slate-react": "^0.111.0", "source-map-support": "^0.5.13", + "vercel": "^33.6.2", "winston": "^3.13.0" }, "repository": { diff --git a/yarn.lock b/yarn.lock index 838d1136e3..66d7f00674 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7798,6 +7798,7 @@ __metadata: tsx: "npm:^4.19.2" typescript: "npm:^5.7.2" typescript-eslint: "npm:^8.16.0" + vercel: "npm:^33.6.2" vite: "npm:^6.0.1" vitest: "npm:^2.1.6" winston: "npm:^3.13.0" From b561e2b09f862a5f3ea2269bdf19ba0c5d856c5a Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Wed, 15 Jan 2025 15:47:19 +0100 Subject: [PATCH 29/30] Move vercel to dev dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b3122252ad..19fd04bf63 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "tsx": "^4.19.2", "typescript": "^5.7.2", "typescript-eslint": "^8.16.0", + "vercel": "^33.6.2", "vite": "^6.0.1", "vitest": "^2.1.6" }, @@ -141,7 +142,6 @@ "slate-hyperscript": "^0.100.0", "slate-react": "^0.111.0", "source-map-support": "^0.5.13", - "vercel": "^33.6.2", "winston": "^3.13.0" }, "repository": { From 81660c7a2522c0eee88dcb179e7087ea3d4e88a4 Mon Sep 17 00:00:00 2001 From: ekrojo77 Date: Wed, 15 Jan 2025 15:58:20 +0100 Subject: [PATCH 30/30] Trigger Build