├── reactjs-gen-ai-apps
├── .eslintignore
├── amplify
│ ├── .config
│ │ ├── local-aws-info.json
│ │ ├── local-env-info.json
│ │ └── project-config.json
│ ├── backend
│ │ ├── api
│ │ │ └── buildingreactjsgenai
│ │ │ │ ├── transform.conf.json
│ │ │ │ ├── schema.graphql
│ │ │ │ ├── cli-inputs.json
│ │ │ │ ├── resolvers
│ │ │ │ └── README.md
│ │ │ │ ├── parameters.json
│ │ │ │ └── stacks
│ │ │ │ └── CustomResources.json
│ │ ├── tags.json
│ │ ├── tsconfig.json
│ │ ├── package.json
│ │ ├── types
│ │ │ └── amplify-dependent-resources-ref.d.ts
│ │ ├── awscloudformation
│ │ │ └── override.ts
│ │ ├── hosting
│ │ │ └── amplifyhosting
│ │ │ │ └── amplifyhosting-template.json
│ │ ├── auth
│ │ │ └── llmchatbotjs6f7265086f726508
│ │ │ │ └── cli-inputs.json
│ │ └── backend-config.json
│ └── cli.json
├── vite.config.js
├── .graphqlconfig.yml
├── src
│ ├── helpers.js
│ ├── ui-components
│ │ ├── studioTheme.js.d.ts
│ │ ├── studioTheme.js
│ │ ├── index.js
│ │ ├── PromptCreateForm.d.ts
│ │ ├── PromptUpdateForm.d.ts
│ │ ├── PromptCreateForm.jsx
│ │ ├── PromptUpdateForm.jsx
│ │ └── utils.js
│ ├── main.jsx
│ ├── App.css
│ ├── graphql
│ │ ├── queries.js
│ │ ├── mutations.js
│ │ └── subscriptions.js
│ ├── BasicNewForm.jsx
│ ├── PromptNew.jsx
│ ├── BedrockKBLoader.jsx
│ ├── index.css
│ ├── FMPicker.jsx
│ ├── Prompt.jsx
│ ├── App.jsx
│ ├── FlashBar.jsx
│ ├── BasicTable.jsx
│ ├── BasicCollection.jsx
│ ├── PromptPicker.jsx
│ ├── Prompts.jsx
│ ├── messageHelpers.js
│ ├── BedrockAgentLoader.jsx
│ ├── fetchHelper.js
│ ├── BedrockAgent.jsx
│ ├── Layout.jsx
│ ├── LLM.jsx
│ ├── __old
│ │ ├── LLM.jsx
│ │ └── Chat.jsx
│ ├── Chat.jsx
│ ├── FileLoader.jsx
│ ├── Menu.jsx
│ ├── questionGenerator.js
│ ├── BedrockKBAndGenerate.jsx
│ ├── assets
│ │ └── react.svg
│ ├── MessageList.jsx
│ ├── BedrockKBRetrieve.jsx
│ ├── MultiModalLLM.jsx
│ ├── PromptCreateFormV2.jsx
│ └── llmLib.js
├── index.html
├── README.md
├── .gitignore
├── public
│ └── vite.svg
└── package.json
├── image.png
├── image-1.png
├── imagenes
├── Q_A.jpg
├── auth.jpg
├── diagram.jpg
├── sing_in.jpg
├── amplify_1.jpg
├── back_end.jpg
├── chat_q_a.jpg
├── demo-LLM.gif
├── demo-q-a.gif
├── demos_menu.jpg
├── new_branch.jpg
├── amplify_done.jpg
├── backend_env.jpg
├── create_user.jpg
├── demo-memory.gif
├── demo-models.jpg
├── demo-prompt.jpg
├── back_end_done.jpg
├── build_settings.jpg
├── knowledge_bases.jpg
├── prompt_diagram.jpg
├── view_in_cognito.jpg
├── chat_with_memory.jpg
├── demo-bedrock-ret.gif
├── prompt_javascript.gif
├── prompt_javascript.jpg
├── repository_branch.jpg
├── bedrock_retrieve_LLM.jpg
├── chat_multimodal_text.gif
├── chat_multimodal_image.gif
├── retrieve_and_generate.jpg
├── chat_with_amazon_bedrock.jpg
├── demo-agent-create-ticket.gif
└── demo-agent-query-ticket.gif
├── CODE_OF_CONDUCT.md
├── .gitignore
├── LICENSE
├── CONTRIBUTING.md
└── README.md
/reactjs-gen-ai-apps/.eslintignore:
--------------------------------------------------------------------------------
1 | amplify-codegen-temp/models/models
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/.config/local-aws-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "staging": {
3 | "configLevel": "amplifyAdmin"
4 | }
5 | }
--------------------------------------------------------------------------------
/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/image.png
--------------------------------------------------------------------------------
/image-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/image-1.png
--------------------------------------------------------------------------------
/imagenes/Q_A.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/Q_A.jpg
--------------------------------------------------------------------------------
/imagenes/auth.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/auth.jpg
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/api/buildingreactjsgenai/transform.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "Version": 5,
3 | "ElasticsearchWarning": true
4 | }
--------------------------------------------------------------------------------
/imagenes/diagram.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/diagram.jpg
--------------------------------------------------------------------------------
/imagenes/sing_in.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/sing_in.jpg
--------------------------------------------------------------------------------
/imagenes/amplify_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/amplify_1.jpg
--------------------------------------------------------------------------------
/imagenes/back_end.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/back_end.jpg
--------------------------------------------------------------------------------
/imagenes/chat_q_a.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/chat_q_a.jpg
--------------------------------------------------------------------------------
/imagenes/demo-LLM.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/demo-LLM.gif
--------------------------------------------------------------------------------
/imagenes/demo-q-a.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/demo-q-a.gif
--------------------------------------------------------------------------------
/imagenes/demos_menu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/demos_menu.jpg
--------------------------------------------------------------------------------
/imagenes/new_branch.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/new_branch.jpg
--------------------------------------------------------------------------------
/imagenes/amplify_done.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/amplify_done.jpg
--------------------------------------------------------------------------------
/imagenes/backend_env.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/backend_env.jpg
--------------------------------------------------------------------------------
/imagenes/create_user.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/create_user.jpg
--------------------------------------------------------------------------------
/imagenes/demo-memory.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/demo-memory.gif
--------------------------------------------------------------------------------
/imagenes/demo-models.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/demo-models.jpg
--------------------------------------------------------------------------------
/imagenes/demo-prompt.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/demo-prompt.jpg
--------------------------------------------------------------------------------
/imagenes/back_end_done.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/back_end_done.jpg
--------------------------------------------------------------------------------
/imagenes/build_settings.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/build_settings.jpg
--------------------------------------------------------------------------------
/imagenes/knowledge_bases.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/knowledge_bases.jpg
--------------------------------------------------------------------------------
/imagenes/prompt_diagram.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/prompt_diagram.jpg
--------------------------------------------------------------------------------
/imagenes/view_in_cognito.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/view_in_cognito.jpg
--------------------------------------------------------------------------------
/imagenes/chat_with_memory.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/chat_with_memory.jpg
--------------------------------------------------------------------------------
/imagenes/demo-bedrock-ret.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/demo-bedrock-ret.gif
--------------------------------------------------------------------------------
/imagenes/prompt_javascript.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/prompt_javascript.gif
--------------------------------------------------------------------------------
/imagenes/prompt_javascript.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/prompt_javascript.jpg
--------------------------------------------------------------------------------
/imagenes/repository_branch.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/repository_branch.jpg
--------------------------------------------------------------------------------
/imagenes/bedrock_retrieve_LLM.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/bedrock_retrieve_LLM.jpg
--------------------------------------------------------------------------------
/imagenes/chat_multimodal_text.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/chat_multimodal_text.gif
--------------------------------------------------------------------------------
/imagenes/chat_multimodal_image.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/chat_multimodal_image.gif
--------------------------------------------------------------------------------
/imagenes/retrieve_and_generate.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/retrieve_and_generate.jpg
--------------------------------------------------------------------------------
/imagenes/chat_with_amazon_bedrock.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/chat_with_amazon_bedrock.jpg
--------------------------------------------------------------------------------
/imagenes/demo-agent-create-ticket.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/demo-agent-create-ticket.gif
--------------------------------------------------------------------------------
/imagenes/demo-agent-query-ticket.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/HEAD/imagenes/demo-agent-query-ticket.gif
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/api/buildingreactjsgenai/schema.graphql:
--------------------------------------------------------------------------------
1 |
2 | type Prompt @model @auth(rules: [{ allow: owner }]) {
3 | id: ID!
4 | name: String!
5 | prompt: String
6 | }
7 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/tags.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Key": "user:Stack",
4 | "Value": "{project-env}"
5 | },
6 | {
7 | "Key": "user:Application",
8 | "Value": "{project-name}"
9 | }
10 | ]
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/.config/local-env-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectPath": "/Users/ensamblador/Documents/proyectos/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/reactjs-gen-ai-apps",
3 | "envName": "staging",
4 | "defaultEditor": "vscode"
5 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "strict": false,
6 | "esModuleInterop": true,
7 | "skipLibCheck": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "outDir": "build"
10 | }
11 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/api/buildingreactjsgenai/cli-inputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "serviceConfiguration": {
4 | "apiName": "buildingreactjsgenai",
5 | "serviceName": "AppSync",
6 | "defaultAuthType": {
7 | "mode": "AMAZON_COGNITO_USER_POOLS",
8 | "cognitoUserPoolId": "authllmchatbotjs6f7265086f726508"
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/api/buildingreactjsgenai/resolvers/README.md:
--------------------------------------------------------------------------------
1 | Any resolvers that you add in this directory will override the ones automatically generated by Amplify CLI and will be directly copied to the cloud.
2 | For more information, visit [https://docs.amplify.aws/cli/graphql-transformer/resolvers](https://docs.amplify.aws/cli/graphql-transformer/resolvers)
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/api/buildingreactjsgenai/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "AppSyncApiName": "buildingreactjsgenai",
3 | "DynamoDBBillingMode": "PAY_PER_REQUEST",
4 | "DynamoDBEnableServerSideEncryption": false,
5 | "AuthCognitoUserPoolId": {
6 | "Fn::GetAtt": [
7 | "authllmchatbotjs6f7265086f726508",
8 | "Outputs.UserPoolId"
9 | ]
10 | }
11 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/.graphqlconfig.yml:
--------------------------------------------------------------------------------
1 | projects:
2 | buildingreactjsgenai:
3 | schemaPath: src/graphql/schema.json
4 | includes:
5 | - src/graphql/**/*.js
6 | excludes:
7 | - ./amplify/**
8 | extensions:
9 | amplify:
10 | codeGenTarget: javascript
11 | generatedFileName: ''
12 | docsFilePath: src/graphql
13 | extensions:
14 | amplify:
15 | version: 3
16 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/helpers.js:
--------------------------------------------------------------------------------
1 | const formatDates = (aDate) => {
2 | const newDate = new Date(aDate).toLocaleString()
3 | return newDate.slice(0, 17)
4 | }
5 |
6 | const formatBool = (val) => {
7 | return val ? "Y" : "N"
8 | }
9 |
10 | const getOportunities = (val) => {
11 | return val.oportunities?.items ? val.oportunities.items.length : 0
12 | }
13 |
14 | export { formatDates, formatBool, getOportunities}
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Bedrock Js demo
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "overrides-dependencies",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build": "tsc",
7 | "watch": "tsc -w",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "dependencies": {
11 | "@aws-amplify/cli-extensibility-helper": "^3.0.0"
12 | },
13 | "devDependencies": {
14 | "typescript": "^4.9.5"
15 | }
16 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/.config/project-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "providers": [
3 | "awscloudformation"
4 | ],
5 | "projectName": "buildingreactjsgenai",
6 | "version": "3.1",
7 | "frontend": "javascript",
8 | "javascript": {
9 | "framework": "react",
10 | "config": {
11 | "SourceDir": "src",
12 | "DistributionDir": "dist",
13 | "BuildCommand": "npm run-script build",
14 | "StartCommand": "npm run-script start"
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/ui-components/studioTheme.js.d.ts:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 | * The contents of this file were generated with Amplify Studio. *
3 | * Please refrain from making any modifications to this file. *
4 | * Any changes to this file will be overwritten when running amplify pull. *
5 | **************************************************************************/
6 |
7 | declare const _default: any;
8 | export default _default;
9 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/ui-components/studioTheme.js:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 | * The contents of this file were generated with Amplify Studio. *
3 | * Please refrain from making any modifications to this file. *
4 | * Any changes to this file will be overwritten when running amplify pull. *
5 | **************************************************************************/
6 |
7 | /* eslint-disable */
8 | import { createTheme, defaultTheme } from "@aws-amplify/ui-react";
9 | export default createTheme(defaultTheme);
10 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/types/amplify-dependent-resources-ref.d.ts:
--------------------------------------------------------------------------------
1 | export type AmplifyDependentResourcesAttributes = {
2 | "api": {
3 | "buildingreactjsgenai": {
4 | "GraphQLAPIEndpointOutput": "string",
5 | "GraphQLAPIIdOutput": "string"
6 | }
7 | },
8 | "auth": {
9 | "llmchatbotjs6f7265086f726508": {
10 | "AppClientID": "string",
11 | "AppClientIDWeb": "string",
12 | "IdentityPoolId": "string",
13 | "IdentityPoolName": "string",
14 | "UserPoolArn": "string",
15 | "UserPoolId": "string",
16 | "UserPoolName": "string"
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/ui-components/index.js:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 | * The contents of this file were generated with Amplify Studio. *
3 | * Please refrain from making any modifications to this file. *
4 | * Any changes to this file will be overwritten when running amplify pull. *
5 | **************************************************************************/
6 |
7 | export { default as PromptCreateForm } from "./PromptCreateForm";
8 | export { default as PromptUpdateForm } from "./PromptUpdateForm";
9 | export { default as studioTheme } from "./studioTheme";
10 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { Amplify } from 'aws-amplify'
4 |
5 | import App from './App.jsx'
6 | import './index.css'
7 | import "@cloudscape-design/global-styles/index.css"
8 | import "@aws-amplify/ui-react/styles.css"
9 | import { ThemeProvider,createTheme, defaultTheme } from "@aws-amplify/ui-react";
10 |
11 |
12 |
13 | import awsconfig from './aws-exports'
14 | Amplify.configure(awsconfig);
15 |
16 |
17 | const studioTheme = createTheme(defaultTheme)
18 |
19 | ReactDOM.createRoot(document.getElementById('root')).render(
20 |
21 |
22 |
23 | )
24 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | /* max-width: 1280px; */
3 | margin: 0 auto;
4 | /* padding: 2rem; */
5 | text-align: left;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/awscloudformation/override.ts:
--------------------------------------------------------------------------------
1 | import { AmplifyProjectInfo, AmplifyRootStackTemplate } from '@aws-amplify/cli-extensibility-helper';
2 |
3 | export function override(resources: AmplifyRootStackTemplate, amplifyProjectInfo: AmplifyProjectInfo) {
4 | const authRole = resources.authRole;
5 | const basePolicies = Array.isArray(authRole.policies) ? authRole.policies : [authRole.policies];
6 | authRole.policies = [ ...basePolicies,
7 | { policyName: "amplify-permissions-custom-resources",
8 | policyDocument: {
9 | Version: "2012-10-17",
10 | Statement: [
11 | {
12 | Resource: "*",
13 | Action: ["bedrock:Invoke*", "bedrock:List*", "bedrock:Retrieve*"],
14 | Effect: "Allow",
15 | }
16 | ]
17 | }
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/graphql/queries.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const getPrompt = /* GraphQL */ `
5 | query GetPrompt($id: ID!) {
6 | getPrompt(id: $id) {
7 | id
8 | name
9 | prompt
10 | createdAt
11 | updatedAt
12 | owner
13 | __typename
14 | }
15 | }
16 | `;
17 | export const listPrompts = /* GraphQL */ `
18 | query ListPrompts(
19 | $filter: ModelPromptFilterInput
20 | $limit: Int
21 | $nextToken: String
22 | ) {
23 | listPrompts(filter: $filter, limit: $limit, nextToken: $nextToken) {
24 | items {
25 | id
26 | name
27 | prompt
28 | createdAt
29 | updatedAt
30 | owner
31 | __typename
32 | }
33 | nextToken
34 | __typename
35 | }
36 | }
37 | `;
38 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/BasicNewForm.jsx:
--------------------------------------------------------------------------------
1 |
2 | import {Container, Header, Box, } from "@cloudscape-design/components"
3 | import { useRef, forwardRef, useImperativeHandle } from 'react'
4 | import FlashBar from './FlashBar'
5 |
6 | export default forwardRef(({ headerText, children }, ref) => {
7 |
8 | const childRef = useRef(null);
9 |
10 | useImperativeHandle(ref, () => ({
11 | showMessage(e, messageType, messageTitle, messageText) {
12 | childRef.current.showMessage(e, messageType, messageTitle, messageText)
13 | }
14 | }))
15 | return (
16 |
17 |
20 | {headerText}}>
21 |
22 | {children}
23 |
24 |
25 | )
26 | })
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/PromptNew.jsx:
--------------------------------------------------------------------------------
1 |
2 | import BasicNewForm from "./BasicNewForm"
3 | import {useRef } from 'react';
4 | import { useNavigate } from "react-router-dom";
5 |
6 | import PromptCreateForm from './PromptCreateFormV2';
7 |
8 | //todo: fix new prompt ID
9 |
10 | export default () => {
11 | let navigate = useNavigate()
12 |
13 | const childRef = useRef(null);
14 | const onSuccess = (e) => {
15 | console.log(e)
16 | childRef.current.showMessage(e, "success", "Success", "Created in Database!")
17 | navigate(`/prompt/${e.id}`)
18 | }
19 | const onError = (e) => childRef.current.showMessage(e, "error", "Error", "Error creating in the Database!")
20 |
21 | return (
22 |
23 |
27 |
28 | )
29 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | *.ipynb
27 | .vscode/
28 | .DS_Store
29 | *.csv
30 | *.drawio
31 |
32 | #amplify-do-not-edit-begin
33 | amplify/\#current-cloud-backend
34 | amplify/.config/local-*
35 | amplify/logs
36 | amplify/mock-data
37 | amplify/mock-api-resources
38 | amplify/backend/amplify-meta.json
39 | amplify/backend/.temp
40 | build/
41 | dist/
42 | node_modules/
43 | aws-exports.js
44 | awsconfiguration.json
45 | amplifyconfiguration.json
46 | amplifyconfiguration.dart
47 | amplify-build-config.json
48 | amplify-gradle-config.json
49 | amplifytools.xcconfig
50 | .secret-*
51 | **.sample
52 | #amplify-do-not-edit-end
53 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/hosting/amplifyhosting/amplifyhosting-template.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "{\"createdOn\":\"Linux\",\"createdBy\":\"Amplify\",\"createdWith\":\"12.11.1\",\"stackType\":\"hosting-amplifyhosting\",\"metadata\":{}}",
4 | "Parameters": {
5 | "env": {
6 | "Type": "String"
7 | },
8 | "appId": {
9 | "Type": "String"
10 | },
11 | "type": {
12 | "Type": "String"
13 | }
14 | },
15 | "Conditions": {
16 | "isManual": {
17 | "Fn::Equals": [
18 | {
19 | "Ref": "type"
20 | },
21 | "manual"
22 | ]
23 | }
24 | },
25 | "Resources": {
26 | "AmplifyBranch": {
27 | "Condition": "isManual",
28 | "Type": "AWS::Amplify::Branch",
29 | "Properties": {
30 | "BranchName": {
31 | "Ref": "env"
32 | },
33 | "AppId": {
34 | "Ref": "appId"
35 | }
36 | }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | #amplify-do-not-edit-begin
27 | amplify/\#current-cloud-backend
28 | amplify/.config/local-*
29 | amplify/logs
30 | amplify/mock-data
31 | amplify/mock-api-resources
32 | amplify/backend/amplify-meta.json
33 | amplify/backend/.temp
34 | build/
35 | dist/
36 | node_modules/
37 | aws-exports.js
38 | awsconfiguration.json
39 | amplifyconfiguration.json
40 | amplifyconfiguration.dart
41 | amplify-build-config.json
42 | amplify-gradle-config.json
43 | amplifytools.xcconfig
44 | .secret-*
45 | **.sample
46 | #amplify-do-not-edit-end
47 | *.ipynb
48 | .vscode/
49 | .DS_Store
50 | *.csv
51 | team-provider-info.json
52 | amplify/team-provider-info.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT No Attribution
2 |
3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 |
18 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/graphql/mutations.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const createPrompt = /* GraphQL */ `
5 | mutation CreatePrompt(
6 | $input: CreatePromptInput!
7 | $condition: ModelPromptConditionInput
8 | ) {
9 | createPrompt(input: $input, condition: $condition) {
10 | id
11 | name
12 | prompt
13 | createdAt
14 | updatedAt
15 | owner
16 | __typename
17 | }
18 | }
19 | `;
20 | export const updatePrompt = /* GraphQL */ `
21 | mutation UpdatePrompt(
22 | $input: UpdatePromptInput!
23 | $condition: ModelPromptConditionInput
24 | ) {
25 | updatePrompt(input: $input, condition: $condition) {
26 | id
27 | name
28 | prompt
29 | createdAt
30 | updatedAt
31 | owner
32 | __typename
33 | }
34 | }
35 | `;
36 | export const deletePrompt = /* GraphQL */ `
37 | mutation DeletePrompt(
38 | $input: DeletePromptInput!
39 | $condition: ModelPromptConditionInput
40 | ) {
41 | deletePrompt(input: $input, condition: $condition) {
42 | id
43 | name
44 | prompt
45 | createdAt
46 | updatedAt
47 | owner
48 | __typename
49 | }
50 | }
51 | `;
52 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/graphql/subscriptions.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // this is an auto generated file. This will be overwritten
3 |
4 | export const onCreatePrompt = /* GraphQL */ `
5 | subscription OnCreatePrompt(
6 | $filter: ModelSubscriptionPromptFilterInput
7 | $owner: String
8 | ) {
9 | onCreatePrompt(filter: $filter, owner: $owner) {
10 | id
11 | name
12 | prompt
13 | createdAt
14 | updatedAt
15 | owner
16 | __typename
17 | }
18 | }
19 | `;
20 | export const onUpdatePrompt = /* GraphQL */ `
21 | subscription OnUpdatePrompt(
22 | $filter: ModelSubscriptionPromptFilterInput
23 | $owner: String
24 | ) {
25 | onUpdatePrompt(filter: $filter, owner: $owner) {
26 | id
27 | name
28 | prompt
29 | createdAt
30 | updatedAt
31 | owner
32 | __typename
33 | }
34 | }
35 | `;
36 | export const onDeletePrompt = /* GraphQL */ `
37 | subscription OnDeletePrompt(
38 | $filter: ModelSubscriptionPromptFilterInput
39 | $owner: String
40 | ) {
41 | onDeletePrompt(filter: $filter, owner: $owner) {
42 | id
43 | name
44 | prompt
45 | createdAt
46 | updatedAt
47 | owner
48 | __typename
49 | }
50 | }
51 | `;
52 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/BedrockKBLoader.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, forwardRef, useImperativeHandle } from "react";
2 | import { Grid, Select } from "@cloudscape-design/components";
3 | import { getBedrockKnowledgeBases } from "./llmLib";
4 | import { formatDates } from "./helpers"
5 |
6 | export default forwardRef(({}, ref) => {
7 | const [knowledgBases, setKnowledgBases] = useState([])
8 | const [selectedOption, setSelectedOption] = useState({ });
9 |
10 | useImperativeHandle(ref, () => ({
11 | getSelectedOption(){
12 | return selectedOption
13 | }
14 | }))
15 |
16 |
17 | useEffect(() => {
18 | getBedrockKnowledgeBases().then(kbs => {
19 | const kbOptions = kbs.map(kb => ({ label: kb.name, value: kb.knowledgeBaseId, iconName: "settings", description: kb.description, tags: ["Status: "+ kb.status, "Update: " + formatDates(kb.updatedAt) ]}))
20 | setKnowledgBases(kbOptions)
21 | setSelectedOption(kbOptions[0])
22 | })
23 |
24 | }, [])
25 |
26 |
27 |
28 | return (
29 |
30 |
33 |
34 |
40 |
41 |
42 | )
43 | })
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/index.css:
--------------------------------------------------------------------------------
1 | #root {
2 | width: 100%;
3 | }
4 |
5 | :root {
6 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
7 | line-height: 1.5;
8 | font-weight: 400;
9 |
10 | color-scheme: light dark;
11 | color: rgba(255, 255, 255, 0.87);
12 | background-color: #242424;
13 |
14 | font-synthesis: none;
15 | text-rendering: optimizeLegibility;
16 | -webkit-font-smoothing: antialiased;
17 | -moz-osx-font-smoothing: grayscale;
18 | }
19 |
20 | a {
21 | font-weight: 500;
22 | color: #646cff;
23 | text-decoration: inherit;
24 | }
25 | a:hover {
26 | color: #535bf2;
27 | }
28 |
29 | body {
30 | margin: 0;
31 | display: flex;
32 | place-items: center;
33 | min-width: 320px;
34 | min-height: 100vh;
35 | }
36 |
37 | h1 {
38 | font-size: 3.2em;
39 | line-height: 1.1;
40 | }
41 |
42 | button {
43 |
44 | padding: 0.6em 1.2em;
45 | font-size: 1em;
46 | font-weight: 500;
47 | font-family: inherit;
48 | background-color: #1a1a1a;
49 | cursor: pointer;
50 | transition: border-color 0.25s;
51 | }
52 | button:hover {
53 | border-color: #646cff;
54 | }
55 | button:focus,
56 | button:focus-visible {
57 | outline: 4px auto -webkit-focus-ring-color;
58 | }
59 |
60 | @media (prefers-color-scheme: light) {
61 | :root {
62 | color: #213547;
63 | background-color: #ffffff;
64 | }
65 | a:hover {
66 | color: #747bff;
67 | }
68 | button {
69 | background-color: #f9f9f9;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/FMPicker.jsx:
--------------------------------------------------------------------------------
1 | import { getFMs } from "./llmLib"
2 | import { useState, useEffect, forwardRef, useImperativeHandle } from "react"
3 | import {SegmentedControl, FormField} from "@cloudscape-design/components"
4 |
5 |
6 | const FMPicker = forwardRef(({ multimodal }, ref) => {
7 | const [selectedId, setSelectedId] = useState("Model 1")
8 | const [models, setModels] = useState([{ modelId: "Model 1"}, { modelId: "Model 1" }, { modelId: "Model 1"}])
9 |
10 |
11 | useImperativeHandle(ref, () => ({
12 | getModelId() {
13 | console.log(selectedId)
14 | return selectedId
15 | }
16 | }))
17 | useEffect(() => {
18 | getFMs().then(res => {
19 | if (multimodal == true) {
20 | res = res.filter(res => res.inputModalities.includes("IMAGE"))
21 | }
22 | setModels(res)
23 | setSelectedId(res[0].modelId)
24 | })
25 |
26 | }, [])
27 |
28 | const getOptions = () => {
29 | const options = models ? models.map(model => {
30 | return { text: model.modelName, id: model.modelId }
31 | }) : []
32 | return options
33 | }
34 |
35 | return (
36 |
37 |
38 | setSelectedId(detail.selectedId)}
41 | options={getOptions()} />
42 |
43 | )
44 | })
45 |
46 | export default FMPicker
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/Prompt.jsx:
--------------------------------------------------------------------------------
1 | import { PromptUpdateForm } from './ui-components'
2 | import { Loader } from "@aws-amplify/ui-react"
3 | import { useParams } from "react-router-dom"
4 | import BasicNewForm from "./BasicNewForm"
5 |
6 | import { useRef, useEffect, useState } from 'react'
7 |
8 | import { fetchById } from './fetchHelper'
9 |
10 |
11 |
12 | export default () => {
13 | const [data, setData] = useState({})
14 | const [isLoading, setIsLoading] = useState(false)
15 |
16 | const childRef = useRef(null);
17 | const onSuccess = (e) => childRef.current.showMessage(
18 | e, "success", "Success", "Updated in the Database!")
19 | const onError = (e) => childRef.current.showMessage(
20 | e, "error", "Error", "Error updating!")
21 |
22 | let { PromptId } = useParams()
23 |
24 | const getModel = async (Id) => {
25 | const model = await fetchById("getPrompt", Id)
26 | console.log(model)
27 | setData(model)
28 | setIsLoading(false)
29 | }
30 |
31 | useEffect(() => {
32 | setIsLoading(true)
33 | console.log("PromptId", PromptId)
34 | getModel(PromptId)
35 |
36 | }, [PromptId])
37 |
38 |
39 |
40 | return (
41 |
42 | {isLoading ? :
43 | }
47 |
48 |
49 |
50 | )
51 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { createBrowserRouter, RouterProvider } from "react-router-dom"
2 | import { withAuthenticator } from '@aws-amplify/ui-react'
3 | import './App.css'
4 | import Menu from "./Menu"
5 | import Layout from './Layout'
6 | import Prompts from "./Prompts"
7 | import PromptNew from "./PromptNew"
8 | import Prompt from "./Prompt"
9 | import BedrockKBAndGenerate from "./BedrockKBAndGenerate"
10 | import BedrockKBRetrieve from "./BedrockKBRetrieve"
11 |
12 | import BedrockAgent from "./BedrockAgent"
13 | import MultiModalLLM from "./MultiModalLLM"
14 |
15 | const App = ({ signOut, user }) => {
16 |
17 | const router = createBrowserRouter([
18 |
19 | {
20 | path: "/",
21 | errorElement: something went wrong!
,
22 | element: ,
23 | children: [
24 | { path: "multimodal", element: },
25 | { path: "retrieveandgenerate", element: },
26 | { path: "prompt", element: },
27 | { path: "prompt/new", element: },
28 | { path: "prompt/:PromptId", element: },
29 | { path: "retrieve", element: },
30 | { path: "bedrockagent", element: },
31 |
32 | ]
33 | }
34 | ])
35 |
36 | return ()
37 | }
38 |
39 | const Struct = ({ signOut, ...user }) =>
40 | [
41 | ,
42 |
43 | ]
44 |
45 | export default withAuthenticator(App, {
46 | hideSignUp: true
47 | })
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/FlashBar.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { Flashbar } from "@cloudscape-design/components"
3 | import { forwardRef, useImperativeHandle } from 'react'
4 |
5 | export default forwardRef(({ }, ref) => {
6 | const [items, setItems] = useState([])
7 | useImperativeHandle(ref, () => ({
8 | showMessage(e, messageType, messageTitle, messageText) {
9 | setItems(items => {
10 | const n = items.length
11 | return [...items, {
12 | type: messageType,
13 | dismissible: true,
14 | dismissLabel: "Dismiss message",
15 | content: messageText,
16 | header: messageTitle,
17 | id: n,
18 | onDismiss: () =>
19 | setItems(items =>
20 | items.filter(item => item.id !== n)
21 | )
22 | }]}
23 | )
24 | }
25 | }))
26 |
27 |
28 | return (
29 |
44 | );
45 | })
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/BasicTable.jsx:
--------------------------------------------------------------------------------
1 | import Table from "@cloudscape-design/components/table"
2 | import Pagination from "@cloudscape-design/components/pagination"
3 | import TextFilter from "@cloudscape-design/components/text-filter"
4 | import Box from "@cloudscape-design/components/box"
5 | import SpaceBetween from "@cloudscape-design/components/space-between"
6 | import { useState } from "react"
7 |
8 | export default ({ columnDefinitions, columnDisplay, itemList, rowClick }) => {
9 | const [filteringText, setFilteringText] = useState("")
10 |
11 | const filteredItemList = () => itemList.filter((item) =>
12 | item.name?.toLowerCase().includes(filteringText.toLowerCase())
13 | || item.title?.toLowerCase().includes(filteringText.toLowerCase())
14 | || item.customer?.toLowerCase().includes(filteringText.toLowerCase())
15 | || item.url?.toLowerCase().includes(filteringText.toLowerCase())
16 | || item.description?.toLowerCase().includes(filteringText.toLowerCase(),
17 | )
18 | )
19 |
20 |
21 | return No hay}
28 | filter={
29 |
33 | setFilteringText(detail.filteringText)}
34 | />
35 | }
36 | pagination={
37 |
38 | }
39 | />
40 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/BasicCollection.jsx:
--------------------------------------------------------------------------------
1 | import Box from "@cloudscape-design/components/box"
2 | import Header from "@cloudscape-design/components/header"
3 | import Container from "@cloudscape-design/components/container"
4 | import SpaceBetween from "@cloudscape-design/components/space-between"
5 | import Button from "@cloudscape-design/components/button"
6 | import BasicTable from "./BasicTable"
7 | import { useNavigate } from "react-router-dom"
8 | import { Loader } from "@aws-amplify/ui-react"
9 |
10 |
11 | export default ({ headerText, itemList,
12 | columnDefinitions, columnDisplay, isLoading = false, newEnabled = true, rowClickable = true, navPrefix = "" }) => {
13 |
14 | let navigate = useNavigate()
15 |
16 | const newItem = () => { navigate(navPrefix + "new") }
17 |
18 | const itemClick = (e) => {
19 | //console.log("item click", e)
20 | if (!rowClickable) return;
21 | navigate(navPrefix + `${e.detail.item.id}`)
22 | }
23 |
24 | return (
25 |
26 |
30 | {newEnabled ? : null}
31 |
32 | }>
33 | {headerText}
34 |
35 | }>
36 | {isLoading ? :
37 |
38 | }
39 |
40 |
41 | )
42 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/PromptPicker.jsx:
--------------------------------------------------------------------------------
1 |
2 | import { useState, useEffect, forwardRef, useImperativeHandle } from "react"
3 | import { Select, Grid, FormField } from "@cloudscape-design/components"
4 | import { fetchByValue } from './fetchHelper'
5 |
6 | export default forwardRef(({ }, ref) => {
7 | const [selectedOption, setSelectedOption] = useState({});
8 | const [prompts, setPrompts] = useState([{ name: "", prompt: false }])
9 |
10 |
11 | useImperativeHandle(ref, () => ({
12 | getPrompt() {
13 | return selectedOption.value
14 | }
15 | }))
16 |
17 |
18 | const getList = async () => {
19 | let list = await fetchByValue("listPrompts")
20 | let first = list[0]
21 | setPrompts(list)
22 | if (first) {
23 | setSelectedOption({ label: first.name, value: first.prompt })
24 | }
25 |
26 | }
27 |
28 | useEffect(() => {
29 | getList()
30 | }, [])
31 |
32 | const getOptions = () => {
33 | const options = prompts ? prompts.map(pr => {
34 | return { label: pr.name, value: pr.prompt }
35 | }) : []
36 | return options
37 | }
38 |
39 | return (
40 |
41 |
42 |
43 |
52 |
53 |
54 |
55 | )
56 | })
57 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bedrock-javascript",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@aws-amplify/ui-react": "^6.1.3",
14 | "@aws-crypto/sha256-js": "^5.2.0",
15 | "@aws-sdk/client-bedrock": "^3.556.0",
16 | "@aws-sdk/client-bedrock-agent": "^3.513.0",
17 | "@aws-sdk/client-bedrock-agent-runtime": "^3.513.0",
18 | "@aws-sdk/client-bedrock-runtime": "^3.556.0",
19 | "@aws-sdk/credential-provider-node": "^3.509.0",
20 | "@aws-sdk/types": "^3.502.0",
21 | "@cloudscape-design/components": "^3.0.521",
22 | "@cloudscape-design/design-tokens": "^3.0.34",
23 | "@cloudscape-design/global-styles": "^1.0.23",
24 | "@langchain/community": "^0.0.26",
25 | "@langchain/core": "^0.1.27",
26 | "@langchain/openai": "^0.0.14",
27 | "@smithy/eventstream-codec": "^2.1.1",
28 | "@smithy/protocol-http": "^3.1.1",
29 | "@smithy/signature-v4": "^2.1.1",
30 | "@smithy/util-utf8": "^2.1.1",
31 | "aws-amplify": "^6.0.28",
32 | "langchain": "^0.1.17",
33 | "marked": "^12.0.2",
34 | "react": "^18.2.0",
35 | "react-dom": "^18.2.0",
36 | "react-router-dom": "^6.22.0"
37 | },
38 | "devDependencies": {
39 | "@types/react": "^18.2.55",
40 | "@types/react-dom": "^18.2.19",
41 | "@vitejs/plugin-react": "^4.2.1",
42 | "eslint": "^8.56.0",
43 | "eslint-plugin-react": "^7.33.2",
44 | "eslint-plugin-react-hooks": "^4.6.0",
45 | "eslint-plugin-react-refresh": "^0.4.5",
46 | "vite": "^5.1.0"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/api/buildingreactjsgenai/stacks/CustomResources.json:
--------------------------------------------------------------------------------
1 | {
2 | "AWSTemplateFormatVersion": "2010-09-09",
3 | "Description": "An auto-generated nested stack.",
4 | "Metadata": {},
5 | "Parameters": {
6 | "AppSyncApiId": {
7 | "Type": "String",
8 | "Description": "The id of the AppSync API associated with this project."
9 | },
10 | "AppSyncApiName": {
11 | "Type": "String",
12 | "Description": "The name of the AppSync API",
13 | "Default": "AppSyncSimpleTransform"
14 | },
15 | "env": {
16 | "Type": "String",
17 | "Description": "The environment name. e.g. Dev, Test, or Production",
18 | "Default": "NONE"
19 | },
20 | "S3DeploymentBucket": {
21 | "Type": "String",
22 | "Description": "The S3 bucket containing all deployment assets for the project."
23 | },
24 | "S3DeploymentRootKey": {
25 | "Type": "String",
26 | "Description": "An S3 key relative to the S3DeploymentBucket that points to the root\nof the deployment directory."
27 | }
28 | },
29 | "Resources": {
30 | "EmptyResource": {
31 | "Type": "Custom::EmptyResource",
32 | "Condition": "AlwaysFalse"
33 | }
34 | },
35 | "Conditions": {
36 | "HasEnvironmentParameter": {
37 | "Fn::Not": [
38 | {
39 | "Fn::Equals": [
40 | {
41 | "Ref": "env"
42 | },
43 | "NONE"
44 | ]
45 | }
46 | ]
47 | },
48 | "AlwaysFalse": {
49 | "Fn::Equals": ["true", "false"]
50 | }
51 | },
52 | "Outputs": {
53 | "EmptyOutput": {
54 | "Description": "An empty output. You may delete this if you have at least one resource above.",
55 | "Value": ""
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/Prompts.jsx:
--------------------------------------------------------------------------------
1 |
2 | import { useState, useEffect } from 'react'
3 |
4 | import { formatDates } from './helpers'
5 | import BasicCollection from './BasicCollection'
6 |
7 | import { fetchByValue } from './fetchHelper'
8 |
9 |
10 | export default () => {
11 | const [itemList, setList] = useState([]);
12 | const [isLoading, setIsLoading] = useState(false)
13 |
14 | const columnDefinitions = [
15 | { id: "id", header: "id", cell: e => e.id, isRowHeader: false },
16 | { id: "name", header: "Name", cell: e => e.name, sortingField: "name", isRowHeader: true },
17 | { id: "prompt", header: "Prompt", cell: e => e.prompt, sortingField: "prompt", isRowHeader: true },
18 | { id: "createdAt", header: "Creation", cell: e => formatDates(e.createdAt), sortingField: "createdAt", isRowHeader: true },
19 | { id: "updatedAt", header: "Update", cell: e => formatDates(e.updatedAt), sortingField: "updatedAt", isRowHeader: true }
20 | ]
21 |
22 | const columnDisplay = [
23 | { id: "name", visible: true },
24 | { id: "prompt", visible: true },
25 | { id: "createdAt", visible: false },
26 | { id: "updatedAt", visible: true }
27 | ]
28 |
29 |
30 |
31 | const getList = async () => {
32 | setIsLoading(true)
33 | setList(await fetchByValue("listPrompts"))
34 | setIsLoading(false)
35 | }
36 |
37 | useEffect(() => {
38 | getList()
39 | }, [])
40 |
41 | return (
42 |
43 |
50 |
51 | )
52 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/auth/llmchatbotjs6f7265086f726508/cli-inputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1",
3 | "cognitoConfig": {
4 | "identityPoolName": "llmchatbotjs6f726508_identitypool_6f726508",
5 | "allowUnauthenticatedIdentities": false,
6 | "resourceNameTruncated": "llmcha6f726508",
7 | "userPoolName": "llmchatbotjs6f726508_userpool_6f726508",
8 | "autoVerifiedAttributes": [
9 | "email"
10 | ],
11 | "mfaConfiguration": "OFF",
12 | "mfaTypes": [
13 | "SMS Text Message"
14 | ],
15 | "smsAuthenticationMessage": "Your authentication code is {####}",
16 | "smsVerificationMessage": "Your verification code is {####}",
17 | "emailVerificationSubject": "Your verification code",
18 | "emailVerificationMessage": "Your verification code is {####}",
19 | "defaultPasswordPolicy": true,
20 | "passwordPolicyMinLength": 8,
21 | "passwordPolicyCharacters": [
22 | "Requires Lowercase",
23 | "Requires Uppercase",
24 | "Requires Numbers",
25 | "Requires Symbols"
26 | ],
27 | "requiredAttributes": [
28 | "email"
29 | ],
30 | "aliasAttributes": [],
31 | "userpoolClientGenerateSecret": false,
32 | "userpoolClientRefreshTokenValidity": "365",
33 | "userpoolClientWriteAttributes": [
34 | "email"
35 | ],
36 | "userpoolClientReadAttributes": [
37 | "email"
38 | ],
39 | "userpoolClientLambdaRole": "llmcha6f726508_userpoolclient_lambda_role",
40 | "userpoolClientSetAttributes": false,
41 | "sharedId": "6f726508",
42 | "resourceName": "llmchatbotjs6f7265086f726508",
43 | "authSelections": "identityPoolAndUserPool",
44 | "useDefault": "manual",
45 | "thirdPartyAuth": false,
46 | "usernameAttributes": [
47 | "email"
48 | ],
49 | "userPoolGroups": false,
50 | "adminQueries": false,
51 | "triggers": {},
52 | "hostedUI": false,
53 | "userPoolGroupList": [],
54 | "serviceName": "Cognito",
55 | "usernameCaseSensitive": false,
56 | "useEnabledMfas": true
57 | }
58 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/messageHelpers.js:
--------------------------------------------------------------------------------
1 | export const getMessageList = (messages) => {
2 | const formatted = messages.map(msg => ({ role: msg.sender, content: [{ type: "text", text: msg.text }] }))
3 | return formatted
4 | }
5 |
6 |
7 |
8 | export const handleStreamingTokenResponse = ({ type, content_block, delta }, llmUpdate, llmEnd, setLoading ) => {
9 | let text = ""
10 | if (type === "content_block_start") text = content_block.text
11 | if (type === "content_block_delta") text = delta.text
12 | llmUpdate(msg => msg + text)//.replace(/\n/g, "
"))
13 |
14 | if (type === "content_block_stop") {
15 | llmUpdate(msg => {
16 | llmEnd(prev => [...prev,{ role: "assistant", content: [{ type: "text", text: msg }] }])
17 | return ""
18 | })
19 | setLoading(false)
20 | }
21 | }
22 |
23 |
24 | const readAsDataURL = (file) => {
25 | return new Promise((resolve, reject) => {
26 | let fileReader = new FileReader();
27 | fileReader.onload = function () {
28 | return resolve({ data: fileReader.result, name: file.name, size: file.size, type: file.type });
29 | }
30 | fileReader.readAsDataURL(file);
31 | })
32 | }
33 |
34 | const loadImages = async (files) => {
35 | let images = await Promise.all(files.map(f => { return readAsDataURL(f) }));
36 | console.log(images)
37 | return images
38 |
39 | }
40 | // todo: add support for png
41 | export const buildContent = async (text, files=[]) => {
42 | let content = [{ type: "text", text: text }]
43 | if (files.length) {
44 | const b64images = await loadImages(files)
45 |
46 | const imgContent = b64images.map(b64i => {
47 | let imageBytes = b64i.data.split(",")[1]
48 | let media_type = b64i.type
49 | return { "type": "image", "source": { "type": "base64", "media_type": media_type, "data": imageBytes } }})
50 |
51 |
52 | content = text=="" ? imgContent: [...imgContent, ...content]
53 | }
54 | return content
55 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/BedrockAgentLoader.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, forwardRef, useImperativeHandle } from "react";
2 | import { Grid, Select } from "@cloudscape-design/components";
3 | import { getBedrockAgents } from "./llmLib";
4 | import { formatDates } from "./helpers"
5 |
6 | export default forwardRef(({ }, ref) => {
7 | const [agents, setAgents] = useState([])
8 | const [selectedOption, setSelectedOption] = useState({});
9 |
10 | useImperativeHandle(ref, () => ({
11 | getSelectedOption() {
12 | return selectedOption
13 | }
14 | }))
15 |
16 | const expandAgents = (agents) => {
17 | const agentsFull = []
18 | agents.forEach(ag => {
19 | ag.aliases.forEach(alias => {
20 | agentsFull.push({ ...ag, alias: alias })
21 | })
22 | })
23 | return agentsFull
24 | }
25 |
26 | useEffect(() => {
27 | getBedrockAgents().then((agents) => {
28 | const ags = expandAgents(agents)
29 | const agOptions = ags.map(ag => {
30 | console.log(ag)
31 | return ({
32 | label: `${ag.agentName} (ID: ${ag.agentId}) ${ag.agentStatus} Actualizado: ${formatDates(ag.updatedAt)}`,
33 | value: ag,
34 | iconName: "gen-ai",
35 | description: ag.status,
36 | tags: [ `Alias: ${ag.alias.agentAliasId} / ${ag.alias.agentAliasStatus}`, "Actualizado: " + formatDates(ag.alias.updatedAt)]
37 | })
38 | })
39 | setAgents(agOptions)
40 | setSelectedOption(agOptions[0])
41 | })
42 |
43 | }, [])
44 |
45 |
46 |
47 | return (
48 |
49 |
52 |
53 |
59 |
60 |
61 | )
62 | })
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "features": {
3 | "graphqltransformer": {
4 | "addmissingownerfields": true,
5 | "improvepluralization": false,
6 | "validatetypenamereservedwords": true,
7 | "useexperimentalpipelinedtransformer": true,
8 | "enableiterativegsiupdates": true,
9 | "secondarykeyasgsi": true,
10 | "skipoverridemutationinputtypes": true,
11 | "transformerversion": 2,
12 | "suppressschemamigrationprompt": true,
13 | "securityenhancementnotification": false,
14 | "showfieldauthnotification": false,
15 | "usesubusernamefordefaultidentityclaim": true,
16 | "usefieldnameforprimarykeyconnectionfield": false,
17 | "enableautoindexquerynames": true,
18 | "respectprimarykeyattributesonconnectionfield": true,
19 | "shoulddeepmergedirectiveconfigdefaults": false,
20 | "populateownerfieldforstaticgroupauth": true
21 | },
22 | "frontend-ios": {
23 | "enablexcodeintegration": true
24 | },
25 | "auth": {
26 | "enablecaseinsensitivity": true,
27 | "useinclusiveterminology": true,
28 | "breakcirculardependency": true,
29 | "forcealiasattributes": false,
30 | "useenabledmfas": true
31 | },
32 | "codegen": {
33 | "useappsyncmodelgenplugin": true,
34 | "usedocsgeneratorplugin": true,
35 | "usetypesgeneratorplugin": true,
36 | "cleangeneratedmodelsdirectory": true,
37 | "retaincasestyle": true,
38 | "addtimestampfields": true,
39 | "handlelistnullabilitytransparently": true,
40 | "emitauthprovider": true,
41 | "generateindexrules": true,
42 | "enabledartnullsafety": true,
43 | "generatemodelsforlazyloadandcustomselectionset": false
44 | },
45 | "appsync": {
46 | "generategraphqlpermissions": true
47 | },
48 | "latestregionsupport": {
49 | "pinpoint": 1,
50 | "translate": 1,
51 | "transcribe": 1,
52 | "rekognition": 1,
53 | "textract": 1,
54 | "comprehend": 1
55 | },
56 | "project": {
57 | "overrides": true
58 | }
59 | },
60 | "debug": {}
61 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/amplify/backend/backend-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "api": {
3 | "buildingreactjsgenai": {
4 | "dependsOn": [
5 | {
6 | "attributes": [
7 | "UserPoolId"
8 | ],
9 | "category": "auth",
10 | "resourceName": "llmchatbotjs6f7265086f726508"
11 | }
12 | ],
13 | "output": {
14 | "authConfig": {
15 | "additionalAuthenticationProviders": [],
16 | "defaultAuthentication": {
17 | "authenticationType": "AMAZON_COGNITO_USER_POOLS",
18 | "userPoolConfig": {
19 | "userPoolId": "authllmchatbotjs6f7265086f726508"
20 | }
21 | }
22 | }
23 | },
24 | "providerPlugin": "awscloudformation",
25 | "service": "AppSync"
26 | }
27 | },
28 | "auth": {
29 | "llmchatbotjs6f7265086f726508": {
30 | "customAuth": false,
31 | "dependsOn": [],
32 | "frontendAuthConfig": {
33 | "mfaConfiguration": "OFF",
34 | "mfaTypes": [
35 | "SMS"
36 | ],
37 | "passwordProtectionSettings": {
38 | "passwordPolicyCharacters": [
39 | "REQUIRES_LOWERCASE",
40 | "REQUIRES_UPPERCASE",
41 | "REQUIRES_NUMBERS",
42 | "REQUIRES_SYMBOLS"
43 | ],
44 | "passwordPolicyMinLength": 8
45 | },
46 | "signupAttributes": [
47 | "EMAIL"
48 | ],
49 | "socialProviders": [],
50 | "usernameAttributes": [
51 | "EMAIL"
52 | ],
53 | "verificationMechanisms": [
54 | "EMAIL"
55 | ]
56 | },
57 | "providerPlugin": "awscloudformation",
58 | "service": "Cognito"
59 | }
60 | },
61 | "hosting": {
62 | "amplifyhosting": {
63 | "providerPlugin": "awscloudformation",
64 | "service": "amplifyhosting",
65 | "type": "manual"
66 | }
67 | },
68 | "parameters": {
69 | "AMPLIFY_hosting_amplifyhosting_appId": {
70 | "usedBy": [
71 | {
72 | "category": "hosting",
73 | "resourceName": "amplifyhosting"
74 | }
75 | ]
76 | },
77 | "AMPLIFY_hosting_amplifyhosting_type": {
78 | "usedBy": [
79 | {
80 | "category": "hosting",
81 | "resourceName": "amplifyhosting"
82 | }
83 | ]
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/fetchHelper.js:
--------------------------------------------------------------------------------
1 | import { generateClient } from "aws-amplify/api"
2 | import * as query from "./graphql/queries"
3 | import { fetchAuthSession } from 'aws-amplify/auth'
4 |
5 | export const getUserId = async () => {
6 | const session = await fetchAuthSession()
7 | console.log("session", session )
8 | let userId = session.identityId
9 | console.log("userId", userId)
10 | return userId;
11 | }
12 |
13 |
14 | export const getClient = (queryName) => {
15 | let client = undefined
16 | let graphqlQuery = ""
17 | if (queryName in query) {
18 | client = generateClient()
19 | graphqlQuery = query[queryName]
20 | }
21 | return { client, graphqlQuery }
22 | }
23 |
24 | export const fetchById = async (queryName, id) => {
25 | const { client, graphqlQuery } = getClient(queryName)
26 | if (client == undefined) {
27 | console.log(`${queryName} not found`)
28 | return undefined
29 | }
30 | console.info("Query:", queryName, "Id:", id)
31 | const variables = { id: id }
32 | const record = await client.graphql(
33 | { query: graphqlQuery.replaceAll("__typename", ""), variables: variables }
34 | )
35 | return record?.data[queryName]
36 | }
37 |
38 | const autocompleteLength = 100
39 |
40 | export const fetchByValue = async (queryName, value= "") => {
41 | const { client, graphqlQuery } = getClient(queryName)
42 | if (client == undefined) {
43 | console.log(`${queryName} not found`)
44 | return undefined
45 | }
46 | const newOptions = []
47 | let newNext = ""
48 | while (newOptions.length < autocompleteLength && newNext != null) {
49 | const variables = {
50 | limit: autocompleteLength * 5,
51 | /* filter: {
52 | or: [{ title: { contains: value } }, { id: { contains: value } }],
53 | }, */
54 | }
55 | if (newNext) {
56 | variables["nextToken"] = newNext
57 | }
58 | const result = await client.graphql({ query: graphqlQuery, variables })
59 | let items = result?.data[queryName]?.items
60 | let nextToken = result?.data[queryName]?.nextToken
61 |
62 | newOptions.push(...items)
63 | newNext = nextToken
64 | //console.log("nextToken", nextToken)
65 | }
66 | //console.log(newOptions)
67 | return newOptions//.slice(0, autocompleteLength)
68 | }
69 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/ui-components/PromptCreateForm.d.ts:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 | * The contents of this file were generated with Amplify Studio. *
3 | * Please refrain from making any modifications to this file. *
4 | * Any changes to this file will be overwritten when running amplify pull. *
5 | **************************************************************************/
6 |
7 | import * as React from "react";
8 | import { GridProps, TextFieldProps } from "@aws-amplify/ui-react";
9 | export declare type EscapeHatchProps = {
10 | [elementHierarchy: string]: Record;
11 | } | null;
12 | export declare type VariantValues = {
13 | [key: string]: string;
14 | };
15 | export declare type Variant = {
16 | variantValues: VariantValues;
17 | overrides: EscapeHatchProps;
18 | };
19 | export declare type ValidationResponse = {
20 | hasError: boolean;
21 | errorMessage?: string;
22 | };
23 | export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise;
24 | export declare type PromptCreateFormInputValues = {
25 | name?: string;
26 | prompt?: string;
27 | };
28 | export declare type PromptCreateFormValidationValues = {
29 | name?: ValidationFunction;
30 | prompt?: ValidationFunction;
31 | };
32 | export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes;
33 | export declare type PromptCreateFormOverridesProps = {
34 | PromptCreateFormGrid?: PrimitiveOverrideProps;
35 | name?: PrimitiveOverrideProps;
36 | prompt?: PrimitiveOverrideProps;
37 | } & EscapeHatchProps;
38 | export declare type PromptCreateFormProps = React.PropsWithChildren<{
39 | overrides?: PromptCreateFormOverridesProps | undefined | null;
40 | } & {
41 | clearOnSuccess?: boolean;
42 | onSubmit?: (fields: PromptCreateFormInputValues) => PromptCreateFormInputValues;
43 | onSuccess?: (fields: PromptCreateFormInputValues) => void;
44 | onError?: (fields: PromptCreateFormInputValues, errorMessage: string) => void;
45 | onChange?: (fields: PromptCreateFormInputValues) => PromptCreateFormInputValues;
46 | onValidate?: PromptCreateFormValidationValues;
47 | } & React.CSSProperties>;
48 | export default function PromptCreateForm(props: PromptCreateFormProps): React.ReactElement;
49 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/ui-components/PromptUpdateForm.d.ts:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 | * The contents of this file were generated with Amplify Studio. *
3 | * Please refrain from making any modifications to this file. *
4 | * Any changes to this file will be overwritten when running amplify pull. *
5 | **************************************************************************/
6 |
7 | import * as React from "react";
8 | import { GridProps, TextFieldProps } from "@aws-amplify/ui-react";
9 | export declare type EscapeHatchProps = {
10 | [elementHierarchy: string]: Record;
11 | } | null;
12 | export declare type VariantValues = {
13 | [key: string]: string;
14 | };
15 | export declare type Variant = {
16 | variantValues: VariantValues;
17 | overrides: EscapeHatchProps;
18 | };
19 | export declare type ValidationResponse = {
20 | hasError: boolean;
21 | errorMessage?: string;
22 | };
23 | export declare type ValidationFunction = (value: T, validationResponse: ValidationResponse) => ValidationResponse | Promise;
24 | export declare type PromptUpdateFormInputValues = {
25 | name?: string;
26 | prompt?: string;
27 | };
28 | export declare type PromptUpdateFormValidationValues = {
29 | name?: ValidationFunction;
30 | prompt?: ValidationFunction;
31 | };
32 | export declare type PrimitiveOverrideProps = Partial & React.DOMAttributes;
33 | export declare type PromptUpdateFormOverridesProps = {
34 | PromptUpdateFormGrid?: PrimitiveOverrideProps;
35 | name?: PrimitiveOverrideProps;
36 | prompt?: PrimitiveOverrideProps;
37 | } & EscapeHatchProps;
38 | export declare type PromptUpdateFormProps = React.PropsWithChildren<{
39 | overrides?: PromptUpdateFormOverridesProps | undefined | null;
40 | } & {
41 | id?: string;
42 | prompt?: any;
43 | onSubmit?: (fields: PromptUpdateFormInputValues) => PromptUpdateFormInputValues;
44 | onSuccess?: (fields: PromptUpdateFormInputValues) => void;
45 | onError?: (fields: PromptUpdateFormInputValues, errorMessage: string) => void;
46 | onChange?: (fields: PromptUpdateFormInputValues) => PromptUpdateFormInputValues;
47 | onValidate?: PromptUpdateFormValidationValues;
48 | } & React.CSSProperties>;
49 | export default function PromptUpdateForm(props: PromptUpdateFormProps): React.ReactElement;
50 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/BedrockAgent.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useRef } from "react"
2 |
3 | import { Box, Spinner, Header, Container, SpaceBetween, Textarea, Button} from "@cloudscape-design/components"
4 | import MessageList from "./MessageList"
5 | import BedrockAgentLoader from "./BedrockAgentLoader";
6 | import {invokeBedrockAgent} from "./llmLib"
7 | import { buildContent } from "./messageHelpers"
8 |
9 |
10 | // create uuid
11 | const createId = () => {
12 | return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
13 | var r = (Math.random() * 16) | 0,
14 | v = c == "x" ? r : (r & 0x3) | 0x8;
15 | return v.toString(16);
16 | });
17 | }
18 |
19 |
20 |
21 |
22 | export default () => {
23 | const [value, setValue] = useState("")
24 | const [loading, setLoading] = useState(false)
25 | const [sessionId, setSessionId] = useState(createId())
26 | const [messages, setMessages] = useState([])
27 |
28 |
29 | const childRef = useRef(null);
30 |
31 | const changeHandler = ({ detail }) => { setValue(detail.value) }
32 |
33 | const sendText = async () => {
34 | setLoading(true)
35 | const currentAgent = childRef.current.getSelectedOption()
36 | console.log(currentAgent)
37 | let content = await buildContent(value, [])
38 | setValue("")
39 | setMessages(prev => [...prev, { content: content, role: "user" }])
40 | const response = await invokeBedrockAgent(sessionId, currentAgent.value.agentId, currentAgent.value.alias.agentAliasId, value)
41 |
42 | let responseContent = await buildContent(response, [])
43 |
44 | setMessages(prev => [...prev, { content: responseContent, role: "assistant" }])
45 | setLoading(false)
46 | setValue("")
47 | }
48 |
49 |
50 |
51 | return (
52 |
53 | Conversacion}>
55 |
56 |
57 |
58 |
59 |
60 | {
61 | messages.length ?
62 |
63 |
64 | {loading ? : null}
65 |
66 | : null
67 |
68 | }
69 |
70 |
77 |
78 |
79 |
80 |
81 |
82 | )
83 | }
84 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/Layout.jsx:
--------------------------------------------------------------------------------
1 | import { Outlet, useNavigate } from "react-router-dom"
2 | import { useState, useEffect } from "react"
3 | import { AppLayout, SideNavigation } from '@cloudscape-design/components';
4 |
5 | export default () => {
6 | const [activeHref, setActiveHref] = useState("/")
7 | useEffect(() => {
8 | setActiveHref("/")
9 | }, [])
10 |
11 |
12 |
13 | let navigate = useNavigate()
14 |
15 |
16 |
17 | return (
18 | LLM`, href: `/retrieve` },
50 | { type: 'link', text: `Amazon Bedrock Retrieve & Generate`, href: `/retrieveandgenerate` }
51 | ]
52 | },
53 | { type: "divider" },
54 | {
55 | type: "section", text: "Agents for Amazon Bedrock", items: [
56 | { type: 'link', text: `Agents`, href: `/bedrockagent` },
57 | ]
58 | }
59 | ]
60 | }
61 | onFollow={event => {
62 | if (!event.detail.external) {
63 | event.preventDefault();
64 | console.log(event.detail.href)
65 | setActiveHref(event.detail.href)
66 | navigate(event.detail.href)
67 | }
68 | }}
69 | />
70 | }
71 | content={}
72 | />)
73 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/LLM.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { Box, Spinner,Header,Container,SpaceBetween, Textarea, Button } from "@cloudscape-design/components"
3 | import MessageList from "./MessageList"
4 |
5 | export default ({llm}) => {
6 | const [chat, setChat] = useState([]);
7 | const [value, setValue] = useState("")
8 | const [loading, setLoading] = useState(false)
9 | const [llmResponse, setLLMResponse] = useState("")
10 |
11 | const handleLLMNewToken = (token) => {
12 | setLLMResponse(msg => msg + token.replace(/\n/g, "
"))
13 | if (token === "") {
14 | setLLMResponse(msg => {
15 | setChat(prev => [...prev, { text: msg, sender: "bot", name: "Demo Bot" }])
16 | return ""
17 | })
18 | setLoading(false)
19 | }
20 | }
21 |
22 | const changeHandler = ({ detail }) => {setValue(detail.value)}
23 | // The changeHandler function is called when the user types in the textarea.
24 |
25 | // code block is making an API call to an AI language model (LLM) to get responses from it.
26 | // It is using the llm.invoke method to pass the user's input value, prefixed with
27 | // "Human:" and suffixed with "Assistant:", to the LLM.
28 | // because the model used is Anthropic's Claude
29 |
30 | const sendText = async () => {
31 | setLoading(true)
32 | setChat(prev => [...prev, { text: value, sender: "user", name: "Demo User" }])
33 | llm.invoke(`Human:\n ${value}\nAssistant:`, { callbacks: [{ handleLLMNewToken }] })
34 | setValue("")
35 | }
36 |
37 | const processKeyUp = (keyCode) => {if (keyCode === 13) sendText()}
38 |
39 | return (
40 |
41 | Conversacion}>
43 |
44 | {
45 | chat.length ?
46 |
47 |
48 | {loading ? : null}
49 |
50 | : null
51 |
52 | }
53 |
54 |
55 | {
56 | llmResponse !== "" ?
57 | Answer}>
58 |
59 | :
60 | null
61 | }
62 |
72 |
73 | )
74 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/__old/LLM.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { Box, Spinner,Header,Container,SpaceBetween, Textarea, Button } from "@cloudscape-design/components"
3 | import MessageList from "./MessageList"
4 |
5 | export default ({llm}) => {
6 | const [chat, setChat] = useState([]);
7 | const [value, setValue] = useState("")
8 | const [loading, setLoading] = useState(false)
9 | const [llmResponse, setLLMResponse] = useState("")
10 |
11 | const handleLLMNewToken = (token) => {
12 | setLLMResponse(msg => msg + token.replace(/\n/g, "
"))
13 | if (token === "") {
14 | setLLMResponse(msg => {
15 | setChat(prev => [...prev, { text: msg, sender: "bot", name: "Demo Bot" }])
16 | return ""
17 | })
18 | setLoading(false)
19 | }
20 | }
21 |
22 | const changeHandler = ({ detail }) => {setValue(detail.value)}
23 | // The changeHandler function is called when the user types in the textarea.
24 |
25 | // code block is making an API call to an AI language model (LLM) to get responses from it.
26 | // It is using the llm.invoke method to pass the user's input value, prefixed with
27 | // "Human:" and suffixed with "Assistant:", to the LLM.
28 | // because the model used is Anthropic's Claude
29 |
30 | const sendText = async () => {
31 | setLoading(true)
32 | setChat(prev => [...prev, { text: value, sender: "user", name: "Demo User" }])
33 | llm.invoke(`Human:\n ${value}\nAssistant:`, { callbacks: [{ handleLLMNewToken }] })
34 | setValue("")
35 | }
36 |
37 | const processKeyUp = (keyCode) => {if (keyCode === 13) sendText()}
38 |
39 | return (
40 |
41 | Conversacion}>
43 |
44 | {
45 | chat.length ?
46 |
47 |
48 | {loading ? : null}
49 |
50 | : null
51 |
52 | }
53 |
54 |
55 | {
56 | llmResponse !== "" ?
57 | Answer}>
58 |
59 | :
60 | null
61 | }
62 |
72 |
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/Chat.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 |
3 | import { Box, Spinner, Header, Container, SpaceBetween, Textarea, Button } from "@cloudscape-design/components"
4 | import MessageList from "./MessageList"
5 | import { BufferMemory } from "langchain/memory";
6 | import { getChain } from "./llmLib";
7 |
8 | // https://js.langchain.com/docs/modules/memory/types/buffer
9 |
10 | const memory = new BufferMemory({ humanPrefix: "H" });
11 |
12 | export default ({ llm }) => {
13 | const [chat, setChat] = useState([]);
14 | const [value, setValue] = useState("")
15 | const [loading, setLoading] = useState(false)
16 | const [llmResponse, setLLMResponse] = useState("")
17 | const [chain, setChain] = useState(null)
18 |
19 | useEffect(() => {
20 | if (llm?.credentials) {
21 | const chain = getChain(llm, memory)
22 | setChain(chain)
23 | }
24 | }, [llm])
25 |
26 |
27 |
28 | const handleLLMNewToken = (token) => {
29 | setLLMResponse(msg => msg + token.replace(/\n/g, "
"))
30 | if (token === "") {
31 | setLLMResponse(msg => {
32 | setChat(prev => [...prev, { text: msg, sender: "bot", name: "Demo Bot" }])
33 | return ""
34 | })
35 | setLoading(false)
36 | }
37 | }
38 |
39 |
40 |
41 | const changeHandler = ({ detail }) => { setValue(detail.value) }
42 |
43 | const sendText = async () => {
44 | setLoading(true)
45 | chain.invoke({ input: value }, { callbacks: [{ handleLLMNewToken }] })
46 | setChat(prev => [...prev, { text: value, sender: "user", name: "Demo User" }])
47 | setValue("")
48 | }
49 |
50 | const processKeyUp = (keyCode) => { if (keyCode === 13) sendText() }
51 |
52 | return (
53 |
54 | Conversacion}>
56 |
57 | {
58 | chat.length ?
59 |
60 |
61 | {loading ? : null}
62 |
63 | : null
64 |
65 | }
66 |
67 |
68 |
69 | {
70 | llmResponse !== "" ?
71 | Respuesta LLM}>
72 |
73 | :
74 | null
75 | }
76 |
86 |
87 |
88 |
89 | )
90 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/__old/Chat.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react"
2 |
3 | import { Box, Spinner, Header, Container, SpaceBetween, Textarea, Button } from "@cloudscape-design/components"
4 | import MessageList from "./MessageList"
5 | import { BufferMemory } from "langchain/memory";
6 | import { getChain } from "./llmLib";
7 |
8 | // https://js.langchain.com/docs/modules/memory/types/buffer
9 |
10 | const memory = new BufferMemory({ humanPrefix: "H" });
11 |
12 | export default ({ llm }) => {
13 | const [chat, setChat] = useState([]);
14 | const [value, setValue] = useState("")
15 | const [loading, setLoading] = useState(false)
16 | const [llmResponse, setLLMResponse] = useState("")
17 | const [chain, setChain] = useState(null)
18 |
19 | useEffect(() => {
20 | if (llm?.credentials) {
21 | const chain = getChain(llm, memory)
22 | setChain(chain)
23 | }
24 | }, [llm])
25 |
26 |
27 |
28 | const handleLLMNewToken = (token) => {
29 | setLLMResponse(msg => msg + token.replace(/\n/g, "
"))
30 | if (token === "") {
31 | setLLMResponse(msg => {
32 | setChat(prev => [...prev, { text: msg, sender: "bot", name: "Demo Bot" }])
33 | return ""
34 | })
35 | setLoading(false)
36 | }
37 | }
38 |
39 |
40 |
41 | const changeHandler = ({ detail }) => { setValue(detail.value) }
42 |
43 | const sendText = async () => {
44 | setLoading(true)
45 | chain.invoke({ input: value }, { callbacks: [{ handleLLMNewToken }] })
46 | setChat(prev => [...prev, { text: value, sender: "user", name: "Demo User" }])
47 | setValue("")
48 | }
49 |
50 | const processKeyUp = (keyCode) => { if (keyCode === 13) sendText() }
51 |
52 | return (
53 |
54 | Conversacion}>
56 |
57 | {
58 | chat.length ?
59 |
60 |
61 | {loading ? : null}
62 |
63 | : null
64 |
65 | }
66 |
67 |
68 |
69 | {
70 | llmResponse !== "" ?
71 | Respuesta LLM}>
72 |
73 | :
74 | null
75 | }
76 |
86 |
87 |
88 |
89 | )
90 | }
91 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/FileLoader.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { FileUpload, FormField, Header, Button, SpaceBetween } from "@cloudscape-design/components";
3 | import { WebPDFLoader } from "langchain/document_loaders/web/pdf";
4 | //import * as pdfjs from "pdfjs-dist/legacy/build/pdf.min.mjs"
5 | //import * as pdfjsWorker from "pdfjs-dist/legacy/build/pdf.worker.min.mjs"
6 | //const pdfjs = await import("pdfjs-dist/legacy/build/pdf.min.mjs")
7 | //const pdfjsWorker = await import("pdfjs-dist/legacy/build/pdf.worker.min.mjs")
8 |
9 | export default () => {
10 | const [value, setValue] = useState([])
11 | const [loading, setLoading] = useState(false)
12 |
13 | const handleOnChange = ({ detail }) => {
14 | console.log(detail.value)
15 | setValue(detail.value)
16 | }
17 | const processDocuments = () => {
18 | value.forEach(async file => {
19 | console.log(file)
20 | const reader = new FileReader()
21 | const blob = new Blob([file], { type: file.type })
22 |
23 | const loader = new WebPDFLoader(blob);
24 | console.log("loader:", loader)
25 | loader.load()
26 | /* if (file.type == "application/pdf") {
27 |
28 | reader.onload = e => {
29 | console.log(e.target.result)
30 |
31 | }
32 |
33 | reader.readAsText(file)
34 | //console.log(blob)
35 | //const blob = await file.arrayBuffer()
36 | //const loader = new PDFLoader(blob)
37 |
38 | //const loader = new WebPDFLoader(blob)
39 | //const docs = await loader.load()
40 | //console.log(docs)
41 |
42 | } */
43 | })
44 | }
45 |
46 | return (
47 |
49 |
50 | : null}
51 | >
52 |
56 |
61 | e ? "Choose files" : "Choose file",
62 | dropzoneText: e =>
63 | e
64 | ? "Drop files to upload"
65 | : "Drop file to upload",
66 | removeFileAriaLabel: e =>
67 | `Remove file ${e + 1}`,
68 | limitShowFewer: "Show fewer files",
69 | limitShowMore: "Show more files",
70 | errorIconAriaLabel: "Error"
71 | }}
72 | multiple
73 | showFileLastModified
74 | showFileSize
75 | showFileThumbnail
76 | tokenLimit={3}
77 | />
78 |
79 |
80 |
81 |
82 | )
83 | }
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/Menu.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import TopNavigation from "@cloudscape-design/components/top-navigation";
3 | import { useNavigate } from "react-router-dom";
4 |
5 |
6 |
7 | const Menu = ({ signOut, groups, ...user }) => {
8 | let navigate = useNavigate()
9 |
10 | const menuFollow = (e) => {
11 | console.log("Menu Follow:", e)
12 | e.preventDefault();
13 | if (e.detail?.href) {
14 | navigate(e.detail.href)
15 | }
16 | }
17 |
18 |
19 | const itemClick = (e) => {
20 | console.log("Logout:", e)
21 | console.log("User:", user)
22 | e.preventDefault();
23 | if (e.detail.id == "signout") signOut()
24 | }
25 |
26 | return (
27 | { navigate("/") }),
31 | title: Amazon Bedrock Javascript
,
32 |
33 | }}
34 | utilities={[
35 | {
36 | type: "button", text: "Chat With Amazon Bedrock",
37 | href: "https://aws.amazon.com/bedrock/", external: true, externalIconAriaLabel: " (opens in a new tab)"
38 | },
39 | {
40 | type: "button", text: "Langchain.js",
41 | href: "https://js.langchain.com/docs/get_started/introduction",
42 | external: true, externalIconAriaLabel: " (opens in a new tab)"
43 | },
44 | {
45 | type: "button", text: "This repo",
46 | href: "https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk",
47 | external: true, externalIconAriaLabel: " (opens in a new tab)"
48 | },
49 | {
50 | type: "menu-dropdown",
51 | text: "Authors",
52 | onItemClick: ((e) => { itemClick(e) }),
53 | iconName: "user-profile",
54 | items: [
55 | {
56 | id: "Eli",
57 | text: "Elizabeth Fuentes Leone",
58 | href: "https://www.linkedin.com/in/lizfue/",
59 | external: true,
60 | externalIconAriaLabel:
61 | " (opens in new tab)"
62 | },
63 | {
64 | id: "Kike",
65 | text: "Enrique Rodríguez",
66 | href: "https://www.linkedin.com/in/enriquerodriguezgarrido/",
67 | external: true,
68 | externalIconAriaLabel:
69 | " (opens in new tab)"
70 | },
71 | ]
72 |
73 | },
74 | {
75 | type: "menu-dropdown",
76 | text: "You",
77 | onItemClick: ((e) => { itemClick(e) }),
78 | iconName: "user-profile",
79 | items: [
80 | { id: "email", text: user.signInDetails?.loginId },
81 | {
82 | id: "signout", text: "Salir"
83 | }
84 | ]
85 |
86 | },
87 |
88 |
89 |
90 | ]}
91 | i18nStrings={{
92 | searchIconAriaLabel: "Search",
93 | searchDismissIconAriaLabel: "Close search",
94 | overflowMenuTriggerText: "More",
95 | overflowMenuTitleText: "All",
96 | overflowMenuBackIconAriaLabel: "Back",
97 | overflowMenuDismissIconAriaLabel: "Close menu"
98 | }}
99 | />
100 |
101 | );
102 | }
103 |
104 |
105 | export default Menu
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/questionGenerator.js:
--------------------------------------------------------------------------------
1 | import { invokeModelStreaming } from "./llmLib"
2 | import { getMessageList } from "./messageHelpers"
3 |
4 | export const questionGenerator = async ({ modelId, messages, question, callbacks }) => {
5 | const newMessages = [...messages, { role: "user", content: [{ type: "text", text: question }] }]
6 | const body = {
7 | "messages": newMessages,
8 | "anthropic_version": "bedrock-2023-05-31",
9 | "max_tokens": 200,
10 | "system": "Given the following conversation, rephrase the last question to be a standalone question. Your answer must be only the question with no preamble."
11 | }
12 | const response = await invokeModelStreaming(body, modelId, { callbacks })
13 | return response
14 | }
15 |
16 | const getContextFromDoc = (doc) => {
17 | let page = doc.metadata?.page ? ` Pag ${doc.metadata.page}` : ""
18 | let source = doc.metadata?.source ? `${doc.metadata.source}${page}` : ""
19 | return `${doc.pageContent}${page}`
20 | }
21 |
22 | export const answerQuestionWithContextFromMemory = async ({ modelId, docs, question, callbacks }) => {
23 | const newMessages = [{ role: "user", content: [{ type: "text", text: question }] }]
24 |
25 | const filteredDocs = docs.filter(doc => doc[1] >= minScore)
26 | console.log(`Docs with score greater than ${minScore}: ${filteredDocs.length}`)
27 |
28 |
29 | let context = docs.map(doc => getContextFromDoc(doc[0])).join("\n")
30 | let system = `Use the following pieces of documents to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. \n\n${context}. Provide sources (in the tags within your response)`
31 | //console.log("context:", context)
32 |
33 | const body = {
34 | "messages": newMessages,
35 | "anthropic_version": "bedrock-2023-05-31",
36 | "max_tokens": 1000,
37 | "system": system
38 | }
39 | const response = await invokeModelStreaming(body, modelId, { callbacks })
40 | return response
41 | }
42 |
43 | export const filterDocsByScore = (docs, minScore) => docs.filter(doc => {
44 | console.log("doc.metadata?.score:", doc.metadata?.score)
45 | if (doc.metadata?.score ){
46 | return doc.metadata.score >= minScore
47 | }
48 | return true
49 | })
50 |
51 |
52 | export const answerQuestionWithContext = async ({ modelId, docs, question, callbacks }) => {
53 | const newMessages = [{ role: "user", content: [{ type: "text", text: question }] }]
54 |
55 |
56 | let context = docs.map(doc => getContextFromDoc(doc)).join("\n")
57 | let system = `Use the following pieces of documents to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. \n\n${context}. Provide sources (in the tags within your response)`
58 | console.log("context:", context)
59 |
60 | const body = {
61 | "messages": newMessages,
62 | "anthropic_version": "bedrock-2023-05-31",
63 | "max_tokens": 1000,
64 | "system": system
65 | }
66 | const response = await invokeModelStreaming(body, modelId, { callbacks })
67 | return response
68 | }
69 |
70 | export const getStandaloneQuestion = async ({ modelId, messages, question, callbacks }) => {
71 | const standaloneQ = await questionGenerator({
72 | modelId: modelId, messages: messages, question: question, callbacks: callbacks
73 | })
74 | return standaloneQ
75 | }
76 |
77 |
78 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/BedrockKBAndGenerate.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useRef } from "react"
2 |
3 | import { Box, Spinner, Header, Container, SpaceBetween, Textarea, Button } from "@cloudscape-design/components"
4 | import MessageList from "./MessageList"
5 | import BedrockKBLoader from "./BedrockKBLoader"
6 | import { ragBedrockKnowledgeBase } from "./llmLib"
7 | import FMPicker from "./FMPicker"
8 | import { buildContent } from "./messageHelpers"
9 |
10 |
11 | export default () => {
12 | const [value, setValue] = useState("")
13 | const [loading, setLoading] = useState(false)
14 | const [sessionId, setSessionId] = useState(null)
15 | const [messages, setMessages] = useState([])
16 |
17 | const childRef = useRef(null)
18 | const childRef2 = useRef(null)
19 |
20 |
21 | const sendText = async () => {
22 | setLoading(true)
23 | const currentKb = childRef.current.getSelectedOption()
24 | const currentModelId = childRef2.current.getModelId()
25 | let content = await buildContent(value, [])
26 | setValue("")
27 | setMessages(prev => [...prev, { role: "user", content: content }])
28 |
29 | const response = await ragBedrockKnowledgeBase(sessionId, currentKb.value, value, currentModelId)
30 | let text = response?.output?.text
31 | setSessionId(response.sessionId)
32 |
33 | let citations = response?.citations.map(citation => {
34 | return citation.retrievedReferences.map(reference => {
35 | let location = reference.location
36 | if (location.type == "S3") return location.s3Location.uri
37 | })
38 | })
39 |
40 | let references = []
41 | citations.forEach(citation => {
42 | if (citation) references.push(...citation)
43 | })
44 | const uniqueReferences = [...new Set(references)]
45 |
46 | uniqueReferences.forEach(ref => {
47 | text += `
${ref}`
48 | })
49 | let responseContent = await buildContent(text, [])
50 | setMessages(prev => [...prev, { role: "assistant", content: responseContent }])
51 | setLoading(false)
52 | }
53 |
54 | const processKeyUp = (keyCode) => { if (keyCode === 13) sendText() }
55 |
56 | return (
57 |
58 | Conversacion}>
60 |
61 |
62 |
63 |
64 |
65 |
66 | {
67 | messages.length ?
68 |
69 |
70 | {loading ? : null}
71 |
72 | : null
73 |
74 | }
75 |
76 |
87 |
88 |
89 |
90 | )
91 | }
92 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/MessageList.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Box, CopyToClipboard, Grid, Container, SpaceBetween } from "@cloudscape-design/components"
3 | import { marked, options } from "marked";
4 |
5 | let config = { startOnLoad: true, flowchart: { useMaxWidth: false, htmlLabels: true } };
6 |
7 |
8 | const renderer = new marked.Renderer();
9 |
10 | renderer.code = function (code, language) {
11 | console.log(language)
12 | if (code.match(/^sequenceDiagram/) || code.match(/^graph/)) {
13 | return '' + code + '
';
14 | } else {
15 | return '' + code + '
';
16 | }
17 | };
18 |
19 |
20 | const UserMessage = ({ msg }) => {
21 | const contentJSX = msg.content.map((item, i) => {
22 |
23 | if (item.type === "text") {
24 | //const html_msg = converter.makeHtml(item.text)
25 | const html_msg = marked.parse(item.text, {renderer: renderer})
26 |
27 | return
28 | }
29 | if (item.type === "image") {
30 | let src = `data:${item.source.media_type};${item.source.type},${item.source.data}`
31 | return
32 | }
33 |
34 | })
35 |
36 | return (
37 |
40 |
41 | {contentJSX}
42 |
43 |
44 | )
45 | }
46 |
47 |
48 | const BotMessage = ({ msg }) => {
49 | const contentJSX = msg.content.map((item, i) => {
50 |
51 | if (item.type === "text") {
52 | //const html_msg = item.text.replace(/\n/g, "
")
53 | //const html_msg = converter.makeHtml(item.text);
54 | const html_msg = marked.parse(item.text, {renderer: renderer})
55 |
56 |
57 | return [
58 | ,
59 | ,
60 | ]
61 | }
62 | if (item.type === "image") {
63 | let src = `data:${item.source.media_type};${item.source.type},${item.source.data}`
64 | return
65 | }
66 |
67 | })
68 | return (
71 | Respuesta LLM}>
72 | {contentJSX}
73 |
74 | )
75 | }
76 |
77 |
78 |
79 | const SystemMessage = ({ msg }) => {
80 | const html_msg = msg.text.replace(/\n/g, "
")
81 | return (
82 |
85 |
86 |
87 |
88 | {msg.name}
89 |
90 | )
91 | }
92 |
93 |
94 |
95 |
96 | const ChatMessage = ({ msg, key }) => {
97 | const role = msg.role
98 | if (role === "user") return
99 | if (role === "assistant") return
100 | if (role === "system") return
101 | }
102 |
103 |
104 | const MessageList = ({ messages }) => {
105 | return {messages.map((msg, i) => )}
106 | }
107 |
108 | export default MessageList
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/BedrockKBRetrieve.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useRef } from "react"
2 |
3 | import { Box, Spinner, Header, Container, SpaceBetween, Textarea, Button } from "@cloudscape-design/components"
4 | import MessageList from "./MessageList"
5 | import BedrockKBLoader from "./BedrockKBLoader";
6 | import { getBedrockKnowledgeBaseRetriever } from "./llmLib"
7 | import FMPicker from "./FMPicker";
8 | import { answerQuestionWithContext, getStandaloneQuestion } from "./questionGenerator";
9 | import { buildContent, handleStreamingTokenResponse } from "./messageHelpers";
10 | import { filterDocsByScore } from "./questionGenerator";
11 |
12 | export default () => {
13 |
14 | const [value, setValue] = useState("")
15 | const [loading, setLoading] = useState(false)
16 | const [llmResponse, setLLMResponse] = useState("")
17 | const [messages, setMessages] = useState([])
18 |
19 | const childRef = useRef(null);
20 | const childRef2 = useRef(null);
21 | const childRef3 = useRef(null);
22 |
23 |
24 | const handleLLMNewToken = ({ type, content_block, delta }) => {
25 | handleStreamingTokenResponse({ type, content_block, delta }, setLLMResponse, setMessages, setLoading)
26 | }
27 |
28 |
29 | const sendText = async () => {
30 | setLoading(true)
31 | let currentKb = childRef.current.getSelectedOption()
32 | let currentModelId = childRef2.current.getModelId()
33 | let content = await buildContent(value, [])
34 | setValue("")
35 | setMessages(prev => [...prev,{ role: "user", content: content }])
36 | const question = await getStandaloneQuestion({modelId:currentModelId, messages:messages, question: value})
37 | console.log("standalone question:", question)
38 | setLLMResponse(msg => msg + `Entendí: ${question}
`)
39 |
40 | const retriever = await getBedrockKnowledgeBaseRetriever(currentKb.value)
41 | const docs = await retriever.invoke(question)
42 |
43 | let minScore = 0.5
44 | const filteredDocs = filterDocsByScore(docs, minScore)
45 | console.log(" docs:", docs)
46 | if (filteredDocs.length === 0) {
47 | let nodocs_msg = "I'm sorry. No Documents, so I don't know the answer to that question."
48 | setLLMResponse(msg => msg + `${nodocs_msg}`)
49 | } else {
50 | const answer = await answerQuestionWithContext({question: question, docs: filteredDocs, callbacks: [{ handleLLMNewToken }]})
51 | console.log(answer)
52 | }
53 |
54 | childRef3.current.focus()
55 | }
56 |
57 |
58 | const processKeyUp = (keyCode) => { if (keyCode === 13) sendText() }
59 |
60 | return (
61 |
62 | Conversacion}>
64 |
65 |
66 |
67 |
68 |
69 |
70 | {
71 | messages.length ?
72 |
73 |
74 | {loading ? : null}
75 |
76 | : null
77 |
78 | }
79 |
80 | {
81 | llmResponse !== "" ?
82 | Respuesta LLM}>
83 |
84 | :
85 | null
86 | }
87 |
100 |
101 |
102 |
103 | )
104 | }
105 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/MultiModalLLM.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useRef, useEffect } from "react"
2 | import { Box, Spinner, Header, Container, SpaceBetween, Textarea, Button, FileUpload } from "@cloudscape-design/components"
3 | import MessageList from "./MessageList"
4 | import FMPicker from "./FMPicker"
5 | import { invokeModelStreaming } from "./llmLib"
6 | import PromptPicker from "./PromptPicker"
7 |
8 |
9 | import { handleStreamingTokenResponse, buildContent } from "./messageHelpers"
10 |
11 |
12 | export default () => {
13 | const [value, setValue] = useState("")
14 | const [files, setFiles] = useState([]);
15 | const [loading, setLoading] = useState(false)
16 | const [llmResponse, setLLMResponse] = useState("")
17 | const [messages, setMessages] = useState([])
18 |
19 |
20 | const newConversation = () => {
21 | setMessages([])
22 | setLLMResponse("")
23 | setValue("")
24 | setLoading(false)
25 | }
26 | const modelPickerRef = useRef(null);
27 | const promptPickerRef = useRef(null);
28 |
29 | const handleLLMNewToken = ({ type, content_block, delta }) => {
30 | handleStreamingTokenResponse({ type, content_block, delta }, setLLMResponse, setMessages, setLoading)
31 | }
32 |
33 |
34 |
35 |
36 | const sendImageAndText = async () => {
37 | const currentModelId = modelPickerRef.current.getModelId()
38 | console.log(currentModelId)
39 | const systemPrompt = promptPickerRef.current.getPrompt()
40 |
41 | setLoading(true)
42 | let content = await buildContent(value, files)
43 | setValue("")
44 | setFiles([])
45 | console.log(content)
46 | setMessages(prev => {
47 | const history = [...prev, { role: "user", content: content }]
48 | const body = {
49 | "messages": history,
50 | "anthropic_version": "bedrock-2023-05-31", "max_tokens": 1000
51 | }
52 | console.log(systemPrompt)
53 | if (systemPrompt) body["system"] = systemPrompt
54 | invokeModelStreaming(body, currentModelId, { callbacks: [{ handleLLMNewToken }] })
55 | return history
56 | })
57 | }
58 |
59 |
60 | return (
61 |
62 |
66 |
67 | }
68 | description="You can combine images and text in the input"
69 | variant="h2">Conversacion}>
70 |
71 |
72 |
73 |
74 |
75 |
76 | {
77 | messages.length ?
78 |
79 |
80 | {loading ? : null}
81 |
82 | : null
83 |
84 | }
85 |
86 | {
87 | llmResponse !== "" ?
88 | Respuesta LLM}>
89 |
90 | :
91 | null
92 | }
93 | { setFiles(detail.value) }}
95 | value={files}
96 | i18nStrings={{ uploadButtonText: e => e ? "Choose files" : "Choose file", dropzoneText: e => e ? "Drop files to upload" : "Drop file to upload", removeFileAriaLabel: e => `Remove file ${e + 1}`, limitShowFewer: "Show fewer files", limitShowMore: "Show more files", errorIconAriaLabel: "Error" }}
97 | showFileLastModified
98 | showFileSize
99 | showFileThumbnail
100 | tokenLimit={3}
101 | constraintText="Images Files" />
102 |
103 |
112 |
113 | )
114 | }
115 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/ui-components/PromptCreateForm.jsx:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 | * The contents of this file were generated with Amplify Studio. *
3 | * Please refrain from making any modifications to this file. *
4 | * Any changes to this file will be overwritten when running amplify pull. *
5 | **************************************************************************/
6 |
7 | /* eslint-disable */
8 | import * as React from "react";
9 | import { Button, Flex, Grid, TextField } from "@aws-amplify/ui-react";
10 | import { fetchByPath, getOverrideProps, validateField } from "./utils";
11 | import { generateClient } from "aws-amplify/api";
12 | import { createPrompt } from "../graphql/mutations";
13 | const client = generateClient();
14 | export default function PromptCreateForm(props) {
15 | const {
16 | clearOnSuccess = true,
17 | onSuccess,
18 | onError,
19 | onSubmit,
20 | onValidate,
21 | onChange,
22 | overrides,
23 | ...rest
24 | } = props;
25 | const initialValues = {
26 | name: "",
27 | prompt: "",
28 | };
29 | const [name, setName] = React.useState(initialValues.name);
30 | const [prompt, setPrompt] = React.useState(initialValues.prompt);
31 | const [errors, setErrors] = React.useState({});
32 | const resetStateValues = () => {
33 | setName(initialValues.name);
34 | setPrompt(initialValues.prompt);
35 | setErrors({});
36 | };
37 | const validations = {
38 | name: [{ type: "Required" }],
39 | prompt: [],
40 | };
41 | const runValidationTasks = async (
42 | fieldName,
43 | currentValue,
44 | getDisplayValue
45 | ) => {
46 | const value =
47 | currentValue && getDisplayValue
48 | ? getDisplayValue(currentValue)
49 | : currentValue;
50 | let validationResponse = validateField(value, validations[fieldName]);
51 | const customValidator = fetchByPath(onValidate, fieldName);
52 | if (customValidator) {
53 | validationResponse = await customValidator(value, validationResponse);
54 | }
55 | setErrors((errors) => ({ ...errors, [fieldName]: validationResponse }));
56 | return validationResponse;
57 | };
58 | return (
59 | {
65 | event.preventDefault();
66 | let modelFields = {
67 | name,
68 | prompt,
69 | };
70 | const validationResponses = await Promise.all(
71 | Object.keys(validations).reduce((promises, fieldName) => {
72 | if (Array.isArray(modelFields[fieldName])) {
73 | promises.push(
74 | ...modelFields[fieldName].map((item) =>
75 | runValidationTasks(fieldName, item)
76 | )
77 | );
78 | return promises;
79 | }
80 | promises.push(
81 | runValidationTasks(fieldName, modelFields[fieldName])
82 | );
83 | return promises;
84 | }, [])
85 | );
86 | if (validationResponses.some((r) => r.hasError)) {
87 | return;
88 | }
89 | if (onSubmit) {
90 | modelFields = onSubmit(modelFields);
91 | }
92 | try {
93 | Object.entries(modelFields).forEach(([key, value]) => {
94 | if (typeof value === "string" && value === "") {
95 | modelFields[key] = null;
96 | }
97 | });
98 | await client.graphql({
99 | query: createPrompt.replaceAll("__typename", ""),
100 | variables: {
101 | input: {
102 | ...modelFields,
103 | },
104 | },
105 | });
106 | if (onSuccess) {
107 | onSuccess(modelFields);
108 | }
109 | if (clearOnSuccess) {
110 | resetStateValues();
111 | }
112 | } catch (err) {
113 | if (onError) {
114 | const messages = err.errors.map((e) => e.message).join("\n");
115 | onError(modelFields, messages);
116 | }
117 | }
118 | }}
119 | {...getOverrideProps(overrides, "PromptCreateForm")}
120 | {...rest}
121 | >
122 | {
128 | let { value } = e.target;
129 | if (onChange) {
130 | const modelFields = {
131 | name: value,
132 | prompt,
133 | };
134 | const result = onChange(modelFields);
135 | value = result?.name ?? value;
136 | }
137 | if (errors.name?.hasError) {
138 | runValidationTasks("name", value);
139 | }
140 | setName(value);
141 | }}
142 | onBlur={() => runValidationTasks("name", name)}
143 | errorMessage={errors.name?.errorMessage}
144 | hasError={errors.name?.hasError}
145 | {...getOverrideProps(overrides, "name")}
146 | >
147 | {
153 | let { value } = e.target;
154 | if (onChange) {
155 | const modelFields = {
156 | name,
157 | prompt: value,
158 | };
159 | const result = onChange(modelFields);
160 | value = result?.prompt ?? value;
161 | }
162 | if (errors.prompt?.hasError) {
163 | runValidationTasks("prompt", value);
164 | }
165 | setPrompt(value);
166 | }}
167 | onBlur={() => runValidationTasks("prompt", prompt)}
168 | errorMessage={errors.prompt?.errorMessage}
169 | hasError={errors.prompt?.hasError}
170 | {...getOverrideProps(overrides, "prompt")}
171 | >
172 |
176 |
185 |
189 |
196 |
197 |
198 |
199 | );
200 | }
201 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/PromptCreateFormV2.jsx:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 | * The contents of this file were generated with Amplify Studio. *
3 | * Please refrain from making any modifications to this file. *
4 | * Any changes to this file will be overwritten when running amplify pull. *
5 | **************************************************************************/
6 |
7 | /* eslint-disable */
8 | import * as React from "react";
9 | import { Button, Flex, Grid, TextField } from "@aws-amplify/ui-react";
10 | import { fetchByPath, getOverrideProps, validateField } from "./ui-components/utils";
11 | import { generateClient } from "aws-amplify/api";
12 | import { createPrompt } from "./graphql/mutations";
13 | const client = generateClient();
14 | export default function PromptCreateForm(props) {
15 | const {
16 | clearOnSuccess = true,
17 | onSuccess,
18 | onError,
19 | onSubmit,
20 | onValidate,
21 | onChange,
22 | overrides,
23 | ...rest
24 | } = props;
25 | const initialValues = {
26 | name: "",
27 | prompt: "",
28 | };
29 | const [name, setName] = React.useState(initialValues.name);
30 | const [prompt, setPrompt] = React.useState(initialValues.prompt);
31 | const [errors, setErrors] = React.useState({});
32 | const resetStateValues = () => {
33 | setName(initialValues.name);
34 | setPrompt(initialValues.prompt);
35 | setErrors({});
36 | };
37 | const validations = {
38 | name: [{ type: "Required" }],
39 | prompt: [],
40 | };
41 | const runValidationTasks = async (
42 | fieldName,
43 | currentValue,
44 | getDisplayValue
45 | ) => {
46 | const value =
47 | currentValue && getDisplayValue
48 | ? getDisplayValue(currentValue)
49 | : currentValue;
50 | let validationResponse = validateField(value, validations[fieldName]);
51 | const customValidator = fetchByPath(onValidate, fieldName);
52 | if (customValidator) {
53 | validationResponse = await customValidator(value, validationResponse);
54 | }
55 | setErrors((errors) => ({ ...errors, [fieldName]: validationResponse }));
56 | return validationResponse;
57 | };
58 | return (
59 | {
65 | event.preventDefault();
66 | let modelFields = {
67 | name,
68 | prompt,
69 | };
70 | const validationResponses = await Promise.all(
71 | Object.keys(validations).reduce((promises, fieldName) => {
72 | if (Array.isArray(modelFields[fieldName])) {
73 | promises.push(
74 | ...modelFields[fieldName].map((item) =>
75 | runValidationTasks(fieldName, item)
76 | )
77 | );
78 | return promises;
79 | }
80 | promises.push(
81 | runValidationTasks(fieldName, modelFields[fieldName])
82 | );
83 | return promises;
84 | }, [])
85 | );
86 | if (validationResponses.some((r) => r.hasError)) {
87 | return;
88 | }
89 | if (onSubmit) {
90 | modelFields = onSubmit(modelFields);
91 | }
92 | try {
93 | Object.entries(modelFields).forEach(([key, value]) => {
94 | if (typeof value === "string" && value === "") {
95 | modelFields[key] = null;
96 | }
97 | });
98 | const prompt = await client.graphql({
99 | query: createPrompt.replaceAll("__typename", ""),
100 | variables: {
101 | input: {
102 | ...modelFields,
103 | },
104 | },
105 | })
106 | if (onSuccess) {
107 | onSuccess(prompt?.data?.createPrompt);
108 | }
109 | if (clearOnSuccess) {
110 | resetStateValues();
111 | }
112 | } catch (err) {
113 | if (onError) {
114 | console.log(err)
115 | const messages = err.errors.map((e) => e.message).join("\n");
116 | onError(modelFields, messages);
117 | }
118 | }
119 | }}
120 | {...getOverrideProps(overrides, "PromptCreateForm")}
121 | {...rest}
122 | >
123 | {
129 | let { value } = e.target;
130 | if (onChange) {
131 | const modelFields = {
132 | name: value,
133 | prompt,
134 | };
135 | const result = onChange(modelFields);
136 | value = result?.name ?? value;
137 | }
138 | if (errors.name?.hasError) {
139 | runValidationTasks("name", value);
140 | }
141 | setName(value);
142 | }}
143 | onBlur={() => runValidationTasks("name", name)}
144 | errorMessage={errors.name?.errorMessage}
145 | hasError={errors.name?.hasError}
146 | {...getOverrideProps(overrides, "name")}
147 | >
148 | {
154 | let { value } = e.target;
155 | if (onChange) {
156 | const modelFields = {
157 | name,
158 | prompt: value,
159 | };
160 | const result = onChange(modelFields);
161 | value = result?.prompt ?? value;
162 | }
163 | if (errors.prompt?.hasError) {
164 | runValidationTasks("prompt", value);
165 | }
166 | setPrompt(value);
167 | }}
168 | onBlur={() => runValidationTasks("prompt", prompt)}
169 | errorMessage={errors.prompt?.errorMessage}
170 | hasError={errors.prompt?.hasError}
171 | {...getOverrideProps(overrides, "prompt")}
172 | >
173 |
177 |
186 |
190 |
197 |
198 |
199 |
200 | );
201 | }
202 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/ui-components/PromptUpdateForm.jsx:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 | * The contents of this file were generated with Amplify Studio. *
3 | * Please refrain from making any modifications to this file. *
4 | * Any changes to this file will be overwritten when running amplify pull. *
5 | **************************************************************************/
6 |
7 | /* eslint-disable */
8 | import * as React from "react";
9 | import { Button, Flex, Grid, TextField } from "@aws-amplify/ui-react";
10 | import { fetchByPath, getOverrideProps, validateField } from "./utils";
11 | import { generateClient } from "aws-amplify/api";
12 | import { getPrompt } from "../graphql/queries";
13 | import { updatePrompt } from "../graphql/mutations";
14 | const client = generateClient();
15 | export default function PromptUpdateForm(props) {
16 | const {
17 | id: idProp,
18 | prompt: promptModelProp,
19 | onSuccess,
20 | onError,
21 | onSubmit,
22 | onValidate,
23 | onChange,
24 | overrides,
25 | ...rest
26 | } = props;
27 | const initialValues = {
28 | name: "",
29 | prompt: "",
30 | };
31 | const [name, setName] = React.useState(initialValues.name);
32 | const [prompt, setPrompt] = React.useState(initialValues.prompt);
33 | const [errors, setErrors] = React.useState({});
34 | const resetStateValues = () => {
35 | const cleanValues = promptRecord
36 | ? { ...initialValues, ...promptRecord }
37 | : initialValues;
38 | setName(cleanValues.name);
39 | setPrompt(cleanValues.prompt);
40 | setErrors({});
41 | };
42 | const [promptRecord, setPromptRecord] = React.useState(promptModelProp);
43 | React.useEffect(() => {
44 | const queryData = async () => {
45 | const record = idProp
46 | ? (
47 | await client.graphql({
48 | query: getPrompt.replaceAll("__typename", ""),
49 | variables: { id: idProp },
50 | })
51 | )?.data?.getPrompt
52 | : promptModelProp;
53 | setPromptRecord(record);
54 | };
55 | queryData();
56 | }, [idProp, promptModelProp]);
57 | React.useEffect(resetStateValues, [promptRecord]);
58 | const validations = {
59 | name: [{ type: "Required" }],
60 | prompt: [],
61 | };
62 | const runValidationTasks = async (
63 | fieldName,
64 | currentValue,
65 | getDisplayValue
66 | ) => {
67 | const value =
68 | currentValue && getDisplayValue
69 | ? getDisplayValue(currentValue)
70 | : currentValue;
71 | let validationResponse = validateField(value, validations[fieldName]);
72 | const customValidator = fetchByPath(onValidate, fieldName);
73 | if (customValidator) {
74 | validationResponse = await customValidator(value, validationResponse);
75 | }
76 | setErrors((errors) => ({ ...errors, [fieldName]: validationResponse }));
77 | return validationResponse;
78 | };
79 | return (
80 | {
86 | event.preventDefault();
87 | let modelFields = {
88 | name,
89 | prompt: prompt ?? null,
90 | };
91 | const validationResponses = await Promise.all(
92 | Object.keys(validations).reduce((promises, fieldName) => {
93 | if (Array.isArray(modelFields[fieldName])) {
94 | promises.push(
95 | ...modelFields[fieldName].map((item) =>
96 | runValidationTasks(fieldName, item)
97 | )
98 | );
99 | return promises;
100 | }
101 | promises.push(
102 | runValidationTasks(fieldName, modelFields[fieldName])
103 | );
104 | return promises;
105 | }, [])
106 | );
107 | if (validationResponses.some((r) => r.hasError)) {
108 | return;
109 | }
110 | if (onSubmit) {
111 | modelFields = onSubmit(modelFields);
112 | }
113 | try {
114 | Object.entries(modelFields).forEach(([key, value]) => {
115 | if (typeof value === "string" && value === "") {
116 | modelFields[key] = null;
117 | }
118 | });
119 | await client.graphql({
120 | query: updatePrompt.replaceAll("__typename", ""),
121 | variables: {
122 | input: {
123 | id: promptRecord.id,
124 | ...modelFields,
125 | },
126 | },
127 | });
128 | if (onSuccess) {
129 | onSuccess(modelFields);
130 | }
131 | } catch (err) {
132 | if (onError) {
133 | const messages = err.errors.map((e) => e.message).join("\n");
134 | onError(modelFields, messages);
135 | }
136 | }
137 | }}
138 | {...getOverrideProps(overrides, "PromptUpdateForm")}
139 | {...rest}
140 | >
141 | {
147 | let { value } = e.target;
148 | if (onChange) {
149 | const modelFields = {
150 | name: value,
151 | prompt,
152 | };
153 | const result = onChange(modelFields);
154 | value = result?.name ?? value;
155 | }
156 | if (errors.name?.hasError) {
157 | runValidationTasks("name", value);
158 | }
159 | setName(value);
160 | }}
161 | onBlur={() => runValidationTasks("name", name)}
162 | errorMessage={errors.name?.errorMessage}
163 | hasError={errors.name?.hasError}
164 | {...getOverrideProps(overrides, "name")}
165 | >
166 | {
172 | let { value } = e.target;
173 | if (onChange) {
174 | const modelFields = {
175 | name,
176 | prompt: value,
177 | };
178 | const result = onChange(modelFields);
179 | value = result?.prompt ?? value;
180 | }
181 | if (errors.prompt?.hasError) {
182 | runValidationTasks("prompt", value);
183 | }
184 | setPrompt(value);
185 | }}
186 | onBlur={() => runValidationTasks("prompt", prompt)}
187 | errorMessage={errors.prompt?.errorMessage}
188 | hasError={errors.prompt?.hasError}
189 | {...getOverrideProps(overrides, "prompt")}
190 | >
191 |
195 |
205 |
209 |
219 |
220 |
221 |
222 | );
223 | }
224 |
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/llmLib.js:
--------------------------------------------------------------------------------
1 | import { fetchAuthSession } from 'aws-amplify/auth'
2 | import { Bedrock } from "@langchain/community/llms/bedrock/web"
3 | import { AmazonKnowledgeBaseRetriever } from "@langchain/community/retrievers/amazon_knowledge_base"
4 | import { ConversationChain, ConversationalRetrievalQAChain } from "langchain/chains"
5 | import { BedrockRuntimeClient, InvokeModelWithResponseStreamCommand } from "@aws-sdk/client-bedrock-runtime"
6 | import { BedrockAgentClient, ListAgentAliasesCommand, ListAgentsCommand, ListKnowledgeBasesCommand } from "@aws-sdk/client-bedrock-agent"
7 | import { BedrockAgentRuntimeClient, RetrieveAndGenerateCommand, RetrieveCommand, InvokeAgentCommand } from "@aws-sdk/client-bedrock-agent-runtime"
8 | import { BedrockClient, ListFoundationModelsCommand } from "@aws-sdk/client-bedrock"
9 |
10 |
11 | export const getModel = async (modelId = "anthropic.claude-instant-v1") => {
12 | const session = await fetchAuthSession()
13 | let region = session.identityId.split(":")[0]
14 | const model = new Bedrock({
15 | model: modelId,
16 | region: region,
17 | streaming: true,
18 | credentials: session.credentials,
19 | modelKwargs: { max_tokens_to_sample: 1000, temperature: 1 },
20 | })
21 | return model
22 | }
23 |
24 | export const invokeModelStreaming = async (body, modelId = "anthropic.claude-instant-v1", { callbacks }) => {
25 | const session = await fetchAuthSession()
26 | let region = session.identityId.split(":")[0]
27 | const client = new BedrockRuntimeClient({ region: region, credentials: session.credentials })
28 | const input = {
29 | body: JSON.stringify(body),
30 | contentType: "application/json",
31 | accept: "application/json",
32 | modelId: modelId
33 | }
34 | console.log(input)
35 | const command = new InvokeModelWithResponseStreamCommand(input)
36 | const response = await client.send(command)
37 |
38 | let decoder = new TextDecoder("utf-8")
39 | let completion = ""
40 | for await (const chunk of response.body) {
41 | const json_chunk = JSON.parse(decoder.decode(chunk.chunk.bytes))
42 | //console.log(json_chunk)
43 | let text = ""
44 | if (json_chunk.type === "content_block_start") text = json_chunk.content_block.text
45 | if (json_chunk.type === "content_block_delta") text = json_chunk.delta.text
46 | completion += text
47 | callbacks?.forEach(callback => {
48 | if (callback?.handleLLMNewToken) {
49 |
50 | callback.handleLLMNewToken(json_chunk)
51 | }
52 | })
53 | continue
54 |
55 | }
56 | return completion
57 |
58 | }
59 |
60 | export const getChain = (llm, memory) => {
61 |
62 | const chain = new ConversationChain({ llm: llm, memory: memory })
63 | chain.prompt.template = `The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.
64 | Current conversation:
65 | {history}
66 |
67 | Human: {input}
68 | Assistant:`
69 | return chain
70 | }
71 |
72 |
73 | export const getBedrockKnowledgeBases = async () => {
74 | const session = await fetchAuthSession()
75 | let region = session.identityId.split(":")[0]
76 | const client = new BedrockAgentClient({ region: region, credentials: session.credentials })
77 | const command = new ListKnowledgeBasesCommand({})
78 | const response = await client.send(command)
79 | return response.knowledgeBaseSummaries
80 | }
81 |
82 |
83 | export const getBedrockAgents = async () => {
84 | const session = await fetchAuthSession()
85 | let region = session.identityId.split(":")[0]
86 |
87 | const client = new BedrockAgentClient({ region: region, credentials: session.credentials })
88 | const command = new ListAgentsCommand({})
89 | const response = await client.send(command)
90 |
91 | const agentWithAliases = await Promise.all(response.agentSummaries.map(async agent => {
92 | const aliases = await getBedrockAgentAliases(client, agent)
93 | agent.aliases = aliases
94 | return agent
95 | }))
96 | return agentWithAliases
97 | }
98 |
99 |
100 |
101 | export const getBedrockAgentAliases = async (client, agent) => {
102 | const agentCommand = new ListAgentAliasesCommand({ agentId: agent.agentId })
103 | const response = await client.send(agentCommand)
104 | return response.agentAliasSummaries
105 | }
106 |
107 |
108 |
109 | export const ragBedrockKnowledgeBase = async (sessionId, knowledgeBaseId, query, modelId = "anthropic.claude-instant-v1") => {
110 | const session = await fetchAuthSession()
111 | let region = session.identityId.split(":")[0]
112 |
113 | const client = new BedrockAgentRuntimeClient({ region: region, credentials: session.credentials })
114 | const input = {
115 | input: { text: query },
116 | retrieveAndGenerateConfiguration: {
117 | type: "KNOWLEDGE_BASE",
118 | knowledgeBaseConfiguration: {
119 | knowledgeBaseId: knowledgeBaseId,
120 | modelArn: `arn:aws:bedrock:${region}::foundation-model/${modelId}`
121 | },
122 | }
123 | }
124 |
125 | if (sessionId) {
126 | input.sessionId = sessionId
127 | }
128 |
129 | const command = new RetrieveAndGenerateCommand(input)
130 |
131 | try {
132 | const response = await client.send(command);
133 | return response;
134 |
135 | } catch (error) {
136 | return { output: { text: "Error: " + error.message }, citations: [], sessionId }
137 | }
138 |
139 | }
140 |
141 | export const invokeBedrockAgent = async (sessionId, agentId, agentAlias, query) => {
142 | const session = await fetchAuthSession()
143 | let region = session.identityId.split(":")[0]
144 |
145 | const client = new BedrockAgentRuntimeClient({ region: region, credentials: session.credentials })
146 | const input = {
147 | sessionId: sessionId,
148 | agentId: agentId,
149 | agentAliasId: agentAlias,
150 | inputText: query
151 | }
152 |
153 | console.log(input)
154 |
155 | const command = new InvokeAgentCommand(input)
156 | const response = await client.send(command,)
157 | console.log("response:", response)
158 |
159 | let completion = ""
160 |
161 | /* for await (const chunk of stream) {
162 | console.log(chunk)
163 | } */
164 |
165 | let decoder = new TextDecoder("utf-8")
166 | for await (const chunk of response.completion) {
167 | console.log("chunk:", chunk)
168 | const text = decoder.decode(chunk.chunk.bytes)
169 | completion += text
170 | console.log(text)
171 | }
172 |
173 | return completion
174 |
175 | }
176 |
177 |
178 | export const retrieveBedrockKnowledgeBase = async (knowledgeBaseId, query) => {
179 | const session = await fetchAuthSession()
180 | let region = session.identityId.split(":")[0]
181 |
182 | const client = new BedrockAgentRuntimeClient({ region: region, credentials: session.credentials })
183 | const input = { // RetrieveRequest
184 | knowledgeBaseId: knowledgeBaseId, // required
185 | retrievalQuery: { // KnowledgeBaseQuery
186 | text: query, // required
187 | },
188 | retrievalConfiguration: { // KnowledgeBaseRetrievalConfiguration
189 | vectorSearchConfiguration: { // KnowledgeBaseVectorSearchConfiguration
190 | numberOfResults: 5, // required
191 | },
192 | }
193 | }
194 |
195 |
196 | const command = new RetrieveCommand(input)
197 | const response = await client.send(command)
198 | return response
199 | }
200 |
201 |
202 | export const getBedrockKnowledgeBaseRetriever = async (knowledgeBaseId) => {
203 | const session = await fetchAuthSession()
204 | let region = session.identityId.split(":")[0]
205 | const retriever = new AmazonKnowledgeBaseRetriever({
206 | topK: 10,
207 | knowledgeBaseId: knowledgeBaseId,
208 | region: region,
209 | clientOptions: { credentials: session.credentials }
210 | })
211 |
212 | return retriever
213 | }
214 |
215 |
216 | export const getConversationalRetrievalQAChain = async (llm, retriever, memory) => {
217 |
218 |
219 | const chain = ConversationalRetrievalQAChain.fromLLM(
220 | llm, retriever = retriever)
221 | chain.memory = memory
222 |
223 | chain.questionGeneratorChain.prompt.template = "Human: " + chain.questionGeneratorChain.prompt.template + "\nAssistant:"
224 |
225 | chain.combineDocumentsChain.llmChain.prompt.template = `Human: Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
226 |
227 | {context}
228 |
229 | Question: {question}
230 | Helpful Answer:
231 | Assistant:`
232 |
233 | return chain
234 | }
235 |
236 | /* It's querying the Amazon Bedrock service to fetch a list of available AI models
237 | from Anthropic that can be used for text generation, based on the provided filters.
238 | The results can then be used to select which model is most appropriate for the task. */
239 |
240 | export const getFMs = async () => {
241 | const session = await fetchAuthSession()
242 | let region = session.identityId.split(":")[0]
243 | const client = new BedrockClient({ region: region, credentials: session.credentials })
244 | const input = { byProvider: "Anthropic", byOutputModality: "TEXT",byInferenceType: "ON_DEMAND"}
245 | const command = new ListFoundationModelsCommand(input)
246 | const response = await client.send(command)
247 | return response.modelSummaries
248 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Building reactjs Generative AI apps with Amazon Bedrock and AWS JavaScript SDK
2 |
3 |
4 | This article was written in colaboration [Enrique Rodriguez](https://github.com/ensamblador)
5 |
6 | > Get ready to embark on an exciting journey as we combine the power of Amazon Bedrock, ReactJS and the AWS JavaScript SDK to create a generative AI application with minimal integration code.
7 |
8 | ---
9 |
10 | Integrating generative AI into existing applications presents challenges. Many developers have limited experience in training foundations models, but the aim is to integrate generative AI capabilities with minimal code changes.
11 |
12 | To solve this, we created an application that integrates the power of generative AI with a call to the [Amazon Bedrock API](https://docs.aws.amazon.com/bedrock/latest/APIReference/welcome.html) from a web application such [SPA](https://developer.mozilla.org/en-US/docs/Glossary/SPA) built with JavaScript and react framework. With no middleware, lowering the barrier for incorporating AI generation through minimal code integration.
13 |
14 | Throughout this tutorial, you'll learn how to utilize [Amazon Cognito](https://aws.amazon.com/pm/cognito/) credentials and IAM Roles to securely access the [Amazon Bedrock](https://aws.amazon.com/bedrock/) API within your ReactJS application built with the [CloudScape](https://cloudscape.design/) design system. We'll guide you through the process of deploying all the necessary resources and hosting the app using [AWS Amplify](https://aws.amazon.com/amplify/), streamlining the setup and deployment process.
15 |
16 | To enhance the flexibility and customization of the foundation model (FM), we'll demonstrate how to assign different roles using [System Prompt](https://docs.anthropic.com/claude/docs/system-prompts). By creating an [Amazon DynamoDB](https://aws.amazon.com/es/dynamodb/) table, you can store and retrieve various roles, enabling you to manage and access distinct System Prompts associated with each role you wish to assign to the FM. This centralized repository approach allows for dynamic role assignment and tailored AI responses based on the selected role.
17 |
18 |
19 |
20 | 
21 |
22 |
23 | ## How Does This Application Work?
24 |
25 | In the [repository of this application](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/), you will find the code ready to deploy the backend and frontend.
26 |
27 | ✅ **Backend:** An Amazon Cognito User Pool and Identity Pool, with an [AWs Identity and Access Managemen Role](https://docs.aws.amazon.com/es_es/IAM/latest/UserGuide/id_roles.html) (IAM Role) that contains the policy with the permissions to invoke Amazon Bedrock.
28 |
29 | ```
30 | { policyName: "amplify-permissions-custom-resources",
31 | policyDocument: {
32 | Version: "2012-10-17",
33 | Statement: [
34 | {
35 | Resource: "*",
36 | Action: ["bedrock:InvokeModel*", "bedrock:List*", "bedrock:Retrieve*"],
37 | Effect: "Allow",
38 | }
39 | ]
40 | }
41 | }
42 | ```
43 |
44 | Check ["Integrating Amazon Cognito authentication and authorization with web and mobile apps" guide](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-integrate-apps.html) for invoking AWS API operations by users authenticated with Amazon Cognito.
45 |
46 | > This permissions can be customized here: [IAM Role Code](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/blob/main/reactjs-gen-ai-apps/amplify/backend/awscloudformation/override.ts)
47 |
48 | ✅ **Frontend:** a reactjs single page application (SPA) built with [CloudScape](https://cloudscape.design/) design system.
49 |
50 | This application comprises 4 demos:
51 |
52 | - Chat with Amazon Bedrock Multimodal.
53 | - System Prompts.
54 | - Knowledge Bases for Amazon Bedrock.
55 | - Agents for Amazon Bedrock.
56 |
57 | 
58 |
59 | All demos have in common the use of the [BedrockRuntimeClient](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/bedrock-runtime/) or [BedrockAgentRuntimeClient](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/bedrock-agent-runtime/) to invoke the Bedrock or BedrockAgent service for a conversational interaction. The [BedrockAgentClient](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/bedrock-agent/) is also used to list current Bedrock KnowledgeBases deployed in the same account.
60 |
61 | ``` Javascript
62 | import { BedrockAgentClient} from "@aws-sdk/client-bedrock-agent"
63 | import { BedrockAgentRuntimeClient} from "@aws-sdk/client-bedrock-agent-runtime"
64 | ```
65 |
66 | [Amazon Bedrock](https://aws.amazon.com/bedrock/) is a fully managed service that offers a choice of high-performing foundation models (FMs) along with a broad set of capabilities that you need to build and scale generative AI applications.
67 |
68 | ### To select Large Language Model
69 |
70 | To invoke a [FM](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html) you need to specify
71 | the region, streaming responses, and API credentials from the [user pool authentication](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html). For model arguments, you specify the model to sample up to 1000 tokens and for more creative and freedom of generation use a temperature of 1. We do it with the `getModel` function of [llmLib.js](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/blob/main/reactjs-gen-ai-apps/src/llmLib.js)
72 |
73 | ```Javascript
74 | export const getModel = async (modelId = "anthropic.claude-instant-v1") => {
75 | const session = await fetchAuthSession(); //Amplify helper to fetch current logged in user
76 | let region = session.identityId.split(":")[0] //
77 | const model = new Bedrock({
78 | model: modelId, // model-id you can try others if you want
79 | region: region, // app region
80 | streaming: true, // this enables to get the response in streaming manner
81 | credentials: session.credentials, // the user credentials that allows to invoke bedrock service
82 | // try to limit to 1000 tokens for generation
83 | // temperature = 1 means more creative and freedom
84 | modelKwargs: { max_tokens_to_sample: 1000, temperature: 1 },
85 | });
86 | return model;
87 | };
88 | ```
89 |
90 | To select the modelID first you list Amazon Bedrock foundation models using [ListFoundationModels](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_ListFoundationModels.html) on `getFMs` function ( [llmLib.js](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/blob/main/reactjs-gen-ai-apps/src/llmLib.js) ). Each FM has its own way of invoking the model, and this blog is only focusing on the multimodal models of Anthropic.
91 |
92 | ```Javascript
93 | export const getFMs = async () => {
94 | const session = await fetchAuthSession()
95 | let region = session.identityId.split(":")[0]
96 | const client = new BedrockClient({ region: region, credentials: session.credentials })
97 | const input = { byProvider: "Anthropic", byOutputModality: "TEXT",byInferenceType: "ON_DEMAND"}
98 | const command = new ListFoundationModelsCommand(input)
99 | const response = await client.send(command)
100 | return response.modelSummaries
101 | }
102 | ```
103 | This code allows you to choose between [Antropic Claude 3](https://www.anthropic.com/news/claude-3-family) Sonnet or Haiku.
104 |
105 | 
106 |
107 | We'll walk you through each demo group to highlight their differences.
108 |
109 | ### Chat With Amazon Bedrock Multimodal
110 |
111 | 
112 |
113 | [InvokeModelWithResponseStream](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_InvokeModelWithResponseStream.html) is used to invoke the Amazon Bedrock model to run inference using the prompt and inference parameters provided in the request body.
114 |
115 | ``` Javascript
116 | const session = await fetchAuthSession()
117 | let region = session.identityId.split(":")[0]
118 | const client = new BedrockRuntimeClient({ region: region, credentials: session.credentials })
119 | const input = {
120 | body: JSON.stringify(body),
121 | contentType: "application/json",
122 | accept: "application/json",
123 | modelId: modelId
124 | }
125 | const command = new InvokeModelWithResponseStreamCommand(input)
126 | const response = await client.send(command)
127 | ```
128 |
129 | In the [previous blog](https://community.aws/content/2cPcmjVFETxNNLUm1LNkqSOHasu/building-reactjs-generative-ai-apps-with-amazon-bedrock-and-aws-javascript-sdk), we referenced [two approaches to invoking](https://community.aws/content/2cPcmjVFETxNNLUm1LNkqSOHasu/building-reactjs-generative-ai-apps-with-amazon-bedrock-and-aws-javascript-sdk#first-chat-with-amazon-bedrock) the model - one focused on simply asking questions and receiving answers, and another for engaging in full conversations with the model. With [Anthropic Claude 3](https://docs.anthropic.com/claude/reference/claude-on-amazon-bedrock) the conversation is handled by the [The Messages API](https://docs.anthropic.com/claude/reference/messages_post): `messages=[{"role": "user", "content": content}]`.
130 |
131 | Each input message must be an object with a `role` (user or assistant) and content. The `content` can be in either a single string or an array of content blocks, each block having its own designated `type` (text or image).
132 |
133 | - `type` equal `text`:
134 |
135 | ```
136 | {"role": "user", "content": [{"type": "text", "text": "Hello, Claude"}]}
137 | ```
138 |
139 | 
140 |
141 | - `type` equal `image`:
142 |
143 | ```
144 | {"role": "user", "content": [
145 | {
146 | "type": "image",
147 | "source": {
148 | "type": "base64",
149 | "media_type": "image/jpeg",
150 | "data": "/9j/4AAQSkZJRg...",
151 | }
152 | },
153 | {"type": "text", "text": "What is in this image?"}
154 | ]}
155 | ```
156 |
157 | 
158 |
159 | This is an example of a body:
160 |
161 | ```
162 | content = [
163 | {"type": "image", "source": {"type": "base64",
164 | "media_type": "image/jpeg", "data": content_image}},
165 | {"type":"text","text":text}
166 | ]
167 | body = {
168 | "system": "You are an AI Assistant, always reply in the original user text language.",
169 | "messages":content,"anthropic_version": anthropic_version,"max_tokens":max_tokens}
170 | ```
171 |
172 |
173 | > 🖼️ Anthropic currently support the base64 source type for images, and the image/jpeg, image/png, image/gif, and image/webp media types. You can see the conversion of images to base64 for this app in `buildContent` function of [messageHelpers.js](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/blob/main/reactjs-gen-ai-apps/src/helpers.js). See [more input examples](https://docs.anthropic.com/claude/reference/messages-examples).
174 |
175 |
176 | ### Create and reuse prompt
177 |
178 | 
179 |
180 | [The Messages API](https://docs.anthropic.com/claude/reference/messages_post) allows us to add context or instructions to the model through a [System Prompt](https://docs.anthropic.com/claude/docs/system-prompts)(system).
181 |
182 |
183 | By utilizing the System Prompt, we can assign the FM a specific role or provide it with prior instructions before feeding it the input. To enable the FM to take on multiple roles, we created a [react component](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/blob/main/reactjs-gen-ai-apps/src/Prompt.jsx) that allows you to generate a System Prompt, store it in an [Amazon DynamoDB](https://aws.amazon.com/es/dynamodb/) table, and then select it when you want to assign that particular role to the FM.
184 |
185 | All the API operations for managing prompts are handled by a [AWS AppSync](https://aws.amazon.com/es/appsync/) GraphQL API endpoint. AWS AppSync allows you to create and manage GraphQL APIs, which provide a flexible and efficient way to fetch and manipulate data from multiple sources through a single endpoint. ([AWS AppSync Tutorial: DynamoDB resolvers](https://docs.aws.amazon.com/appsync/latest/devguide/tutorial-dynamodb-resolvers.html))
186 |
187 | 
188 |
189 | Let's review an example of a prompt where we tell the FM that he is an expert in JavaScript:
190 |
191 | 
192 |
193 | In the following gif, the model provides code and detailed explanation, like an expert.
194 |
195 | 
196 |
197 | ### Knowledge Bases for Amazon Bedrock
198 |
199 | In this demo, you will ask questions to the [Knowledge Bases for Amazon Bedrock](https://aws.amazon.com/bedrock/knowledge-bases/) taking advantage of [retrieval augmented generation (RAG)](https://aws.amazon.com/what-is/retrieval-augmented-generation/). You must have at least one knowledge base created, do it by following [Create a knowledge base guide](https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-create.html).
200 |
201 | Questions to the Knowledge Bases for Amazon Bedrock will be asked in two ways:
202 |
203 | 
204 |
205 | **- Amazon Bedrock Retrieve => LLM:**
206 |
207 | 
208 |
209 | List the knowledge bases with [ListKnowledgeBasesCommand](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-bedrock-agent/Class/ListKnowledgeBasesCommand/) as follows:
210 |
211 | ```Javascript
212 | import { ListKnowledgeBasesCommand } from "@aws-sdk/client-bedrock-agent"
213 |
214 | export const getBedrockKnowledgeBases = async () => {
215 | const session = await fetchAuthSession()
216 | let region = session.identityId.split(":")[0]
217 | const client = new BedrockAgentClient({ region: region, credentials: session.credentials })
218 | const command = new ListKnowledgeBasesCommand({})
219 | const response = await client.send(command)
220 | return response.knowledgeBaseSummaries
221 | }
222 | ```
223 | The [AmazonKnowledgeBaseRetriever](https://js.langchain.com/docs/integrations/retrievers/bedrock-knowledge-bases) Langchain class creates a retriever, an object capable to retrieve documents similar to a query from a knowledge base (in this case is a Knowledge Base from Bedrock)
224 |
225 |
226 |
227 | ```Javascript
228 | import { AmazonKnowledgeBaseRetriever } from "@langchain/community/retrievers/amazon_knowledge_base";
229 |
230 | export const getBedrockKnowledgeBaseRetriever = async (knowledgeBaseId) => {
231 | const session = await fetchAuthSession();
232 | let region = session.identityId.split(":")[0]
233 | const retriever = new AmazonKnowledgeBaseRetriever({
234 | topK: 10, // return top 10 documents
235 | knowledgeBaseId: knowledgeBaseId,
236 | region: region,
237 | clientOptions: { credentials: session.credentials }
238 | })
239 |
240 | return retriever
241 | }
242 | ```
243 |
244 | The [ConversationalRetrievalQAChain](https://api.js.langchain.com/classes/langchain_chains.ConversationalRetrievalQAChain.html) is instantiated with the retriever and the memory. It takes care of the memory, query the retriever and formulate the answer (with the documents) using the llm instance.
245 |
246 | ```Javascript
247 | import { ConversationalRetrievalQAChain } from "langchain/chains";
248 |
249 | export const getConversationalRetrievalQAChain = async (llm, retriever, memory) => {
250 |
251 | const chain = ConversationalRetrievalQAChain.fromLLM(
252 | llm, retriever = retriever)
253 | chain.memory = memory
254 |
255 | //Here you modify the default prompt to add the Human prefix and Assistant suffix needed by Claude.
256 | //otherwise you get an exception
257 | //this is the prompt that uses chat history and last question to formulate a complete standalone question
258 |
259 | chain.questionGeneratorChain.prompt.template = "Human: " + chain.questionGeneratorChain.prompt.template +"\nAssistant:"
260 | // Here you finally answer the question using the retrieved documents.
261 |
262 | chain.combineDocumentsChain.llmChain.prompt.template = `Human: Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer.
263 |
264 | {context}
265 |
266 | Question: {question}
267 | Helpful Answer:
268 | Assistant:`
269 |
270 | return chain
271 | }
272 | ```
273 | 
274 |
275 | > **Code** [BedrockKBRetrieve.jsx](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/blob/main/reactjs-gen-ai-apps/src/BedrockKBRetrieve.jsx)
276 |
277 | **- Amazon Bedrock Retrieve & Generate:**
278 |
279 | Here you will use a complete AWS Managed RAG service. There is no need for extra packages (Langchain) or increased complexity with prompts. You will use only one API Call to **BedrockAgentRuntimeClient**. Also the memory is managed by the service by using a **sessionId**.
280 |
281 | 
282 |
283 |
284 | Bedrock is initialized with [BedrockAgentRuntimeClient](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-bedrock-agent-runtime/Class/BedrockAgentRuntimeClient/) and with [RetrieveAndGenerateCommand](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-bedrock-agent-runtime/Class/RetrieveAndGenerateCommand/) queries a knowledge base and a foundation model generates responses based on the retrieved results. In this demo Langchain is no needed.
285 |
286 | ```Javascript
287 | import { BedrockAgentRuntimeClient, RetrieveAndGenerateCommand } from "@aws-sdk/client-bedrock-agent-runtime"
288 |
289 | export const ragBedrockKnowledgeBase = async (sessionId, knowledgeBaseId, query, modelId = "anthropic.claude-instant-v1") => {
290 | const session = await fetchAuthSession()
291 | let region = session.identityId.split(":")[0]
292 | const client = new BedrockAgentRuntimeClient({ region: region, credentials: session.credentials });
293 | const input = {
294 | input: { text: query }, // user question
295 | retrieveAndGenerateConfiguration: {
296 | type: "KNOWLEDGE_BASE",
297 | knowledgeBaseConfiguration: {
298 | knowledgeBaseId: knowledgeBaseId,
299 | //your existing KnowledgeBase in the same region/ account
300 | // Arn of a Bedrock model, in this case we jump to claude 2.1, the latest. Feel free to use another
301 | modelArn: `arn:aws:bedrock:${region}::foundation-model/${modelId}`, // Arn of a Bedrock model
302 | },
303 | }
304 | }
305 |
306 | if (sessionId) {
307 | // you can pass the sessionId to continue a dialog.
308 | input.sessionId = sessionId
309 | }
310 |
311 | const command = new RetrieveAndGenerateCommand(input);
312 | const response = await client.send(command)
313 | return response
314 | }
315 | ```
316 | 
317 |
318 | > **Code** [BedrockKBAndGenerate.jsx](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/blob/main/reactjs-gen-ai-apps/src/BedrockKBAndGenerate.jsx)
319 |
320 | ### Agents for Amazon Bedrock
321 |
322 | An [Amazon Bedrock agent](https://aws.amazon.com/bedrock/agents/) is a software component that utilizes the AI models provided by the Amazon Bedrock service to deliver user-facing functionalities, such as chatbots, virtual assistants, or text generation tools. These agents can be customized and adapted to the specific needs of each application, providing a user interface for end-users to interact with the underlying AI capabilities. Bedrock agents handle the integration with the language models, processing user inputs, generating responses, and potentially other actions based on the output of the AI models.
323 |
324 | To integrate Amazon Bedrock agents into this application you must create one, follow the steps [Create an agent in Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-create.html)
325 |
326 | In Amazon Bedrock, you can create a new version of your agent by [creating an alias](https://docs.aws.amazon.com/bedrock/latest/userguide/agents-deploy.html) that points to the new version by default, aliases are listed with [ListAgentAliasesCommand](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/bedrock-agent/command/ListAgentAliasesCommand/)( [llmLib.js](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/blob/main/reactjs-gen-ai-apps/src/llmLib.js) ) :
327 |
328 | ```JavaScript
329 | import { BedrockAgentClient, ListAgentAliasesCommand } from "@aws-sdk/client-bedrock-agent";
330 |
331 | const client = new BedrockAgentRuntimeClient({ region: region, credentials: session.credentials })
332 |
333 | export const getBedrockAgentAliases = async (client, agent) => {
334 | const agentCommand = new ListAgentAliasesCommand({ agentId: agent.agentId })
335 | const response = await client.send(agentCommand)
336 | return response.agentAliasSummaries
337 | }
338 | ```
339 |
340 | To sends a prompt for the agent to process and respond use [InvokeAgentCommand](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/bedrock-agent-runtime/command/InvokeAgentCommand/)
341 |
342 | ```JavaScript
343 | import { BedrockAgentRuntimeClient, InvokeAgentCommand } from "@aws-sdk/client-bedrock-agent-runtime";
344 |
345 | export const invokeBedrockAgent = async (sessionId, agentId, agentAlias, query) => {
346 | const session = await fetchAuthSession()
347 | let region = session.identityId.split(":")[0]
348 |
349 | const client = new BedrockAgentRuntimeClient({ region: region, credentials: session.credentials })
350 | const input = {
351 | sessionId: sessionId,
352 | agentId: agentId,
353 | agentAliasId: agentAlias,
354 | inputText: query
355 | }
356 |
357 | console.log(input)
358 |
359 | const command = new InvokeAgentCommand(input)
360 | const response = await client.send(command,)
361 | console.log("response:", response)
362 |
363 | let completion = ""
364 |
365 | let decoder = new TextDecoder("utf-8")
366 | for await (const chunk of response.completion) {
367 | console.log("chunk:", chunk)
368 | const text = decoder.decode(chunk.chunk.bytes)
369 | completion += text
370 | console.log(text)
371 | }
372 |
373 | return completion
374 |
375 | }
376 | ```
377 | In the agent of this first gif, create a ticket for technical support:
378 |
379 | 
380 |
381 | In the second gif the user asks the agent about the status of the ticket:
382 |
383 | 
384 |
385 | ## Let's Deploy React Generative AI Application With Amazon Bedrock and AWS Javascript SDK
386 |
387 | ### Step 1 - Enable AWS Amplify Hosting:
388 |
389 | The application is built with [AWS Amplify](https://docs.aws.amazon.com/amplify/latest/userguide/welcome.html). To deploy it in your account:
390 |
391 | 1. first [fork](https://docs.github.com/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) this repo:
392 |
393 | ```
394 | https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/forks
395 | ```
396 |
397 | 2. Create a [New branch](https://docs.github.com/en/desktop/making-changes-in-a-branch/managing-branches-in-github-desktop): `dev-branch`.
398 |
399 | 3. Then follow the steps in [Getting started with existing code guide](https://docs.aws.amazon.com/amplify/latest/userguide/getting-started.html).
400 |
401 | 4. In **Step 1 Add repository branch**, select main branch and **Connecting a monorepo? Pick a folder** and enter `reactjs-gen-ai-apps` as a root directory.
402 |
403 | 
404 |
405 | 5. For the next Step, **Build settings**, select `building-a-gen-ai-gen-ai-personal-assistant-reactjs-apps(this app)` as App name, in Enviroment select **Create a new envitoment** and write `dev`
406 |
407 | 
408 |
409 | 6. If there is no existing role, create a [new one to service Amplify](https://docs.aws.amazon.com/amplify/latest/userguide/how-to-service-role-amplify-console.html).
410 |
411 | 7. Deploy your app.
412 |
413 | ### Step 2 - Access to the App URL:
414 |
415 | Once the application has been deployed, go to the link in the application, which is located under the white box.
416 |
417 | 
418 |
419 | When you enter the link, the Sing In window will appear, so you must create a [Amazon Cognito User Pool](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools.html) User.
420 |
421 | 
422 |
423 | #### ✅ How To Create A User
424 |
425 | In the App go to **Backend environments** and click on Authentication.
426 |
427 | 
428 |
429 | Then, under Authentication, click **View in Cognito**:
430 |
431 | 
432 |
433 | In the **User Pool**, click the name of your user pool and **Create User**.
434 |
435 | [Create your user](https://docs.aws.amazon.com/cognito/latest/developerguide/managing-users.html?icmpid=docs_cognito_console_help_panel) and then sing in.
436 |
437 | > **Note:** You can create the user directly from the application by changing False `hideSignUp: false` in [App.jsx](https://github.com/build-on-aws/building-reactjs-gen-ai-apps-with-amazon-bedrock-javascript-sdk/blob/main/reactjs-gen-ai-apps/src/App.jsx), but this can introduce a security flaw by giving anyone access to it.
438 |
439 | ## Let's Try React Generative AI Application With Amazon Bedrock Javascript SDK
440 |
441 | Before you can use a foundation model in Amazon Bedrock, you must request access to it. Follow the step in [Add model access guide](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html).
442 |
443 | Go to the application link and sign in with the user you created.
444 |
445 | ### 🤖🚀 Try and test the app!
446 |
447 | ## Conclusion
448 |
449 | In this post, we demonstrated how you can build a React web application that directly accesses the Amazon Bedrock API using Amazon Cognito for secure authentication. By leveraging AWS managed services like Cognito and IAM, you can seamlessly integrate powerful generative AI capabilities into your javascript applications without the need for backend code.
450 |
451 | This approach allows developers to focus on creating engaging conversational experiences while taking advantage of Amazon Bedrock's managed knowledge service. The streaming responses enhance the user experience by reducing wait times and enabling more natural interactions with conversational AI.
452 |
453 | Furthermore, we showed how you can assign multiple roles to the foundation model using System Prompts stored in an Amazon DynamoDB table. This centralized repository provides flexibility and versatility, allowing you to efficiently retrieve and assign distinct roles to the model based on your specific use case.
454 |
455 | By following the steps outlined in this post, you can unlock the potential of generative AI in your React applications. Whether you're building a new app from scratch or enhancing an existing one, Amazon Bedrock and the AWS JavaScript SDK make it easier than ever to incorporate cutting-edge AI capabilities.
456 |
457 | We encourage you to explore the code samples and resources provided to start building your own generative AI applications. If you have any questions or feedback, please leave a comment below. Happy coding!
458 |
459 | ## 🚀 Some links for you to continue learning and building:
460 |
461 | - [Amplify Workshops](https://workshops.aws/categories/Amplify)
462 | - [Amplify JavaScript Sample Applications](https://github.com/aws-amplify/amplify-js-samples)
463 | - [Actions and scenarios using SDK for JavaScript](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/javascript_code_examples_categorized.html)
464 |
465 | ## Security
466 |
467 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
468 |
469 | ## License
470 |
471 | This library is licensed under the MIT-0 License. See the LICENSE file.
472 |
473 | [def]: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-integrate-apps.html
--------------------------------------------------------------------------------
/reactjs-gen-ai-apps/src/ui-components/utils.js:
--------------------------------------------------------------------------------
1 | /***************************************************************************
2 | * The contents of this file were generated with Amplify Studio. *
3 | * Please refrain from making any modifications to this file. *
4 | * Any changes to this file will be overwritten when running amplify pull. *
5 | **************************************************************************/
6 |
7 | /* eslint-disable */
8 | import * as React from "react";
9 | import { fetchUserAttributes, signOut } from "aws-amplify/auth";
10 | import { DataStore } from "aws-amplify/datastore";
11 | import { Hub } from "aws-amplify/utils";
12 | export const UI_CHANNEL = "ui";
13 | export const UI_EVENT_TYPE_ACTIONS = "actions";
14 | export const CATEGORY_AUTH = "auth";
15 | export const CATEGORY_DATASTORE = "datastore";
16 | export const CATEGORY_CORE = "core";
17 | export const ACTION_AUTH_SIGNOUT = "signout";
18 | export const ACTION_NAVIGATE = "navigate";
19 | export const ACTION_DATASTORE_CREATE = "create";
20 | export const ACTION_DATASTORE_DELETE = "delete";
21 | export const ACTION_DATASTORE_UPDATE = "update";
22 | export const ACTION_STATE_MUTATION = "statemutation";
23 | export const STATUS_STARTED = "started";
24 | export const STATUS_FINISHED = "finished";
25 | export const EVENT_ACTION_AUTH = `${UI_EVENT_TYPE_ACTIONS}:${CATEGORY_AUTH}`;
26 | export const EVENT_ACTION_AUTH_SIGNOUT = `${EVENT_ACTION_AUTH}:${ACTION_AUTH_SIGNOUT}`;
27 | export const ACTION_AUTH_SIGNOUT_STARTED = `${EVENT_ACTION_AUTH_SIGNOUT}:${STATUS_STARTED}`;
28 | export const ACTION_AUTH_SIGNOUT_FINISHED = `${EVENT_ACTION_AUTH_SIGNOUT}:${STATUS_FINISHED}`;
29 | export const EVENT_ACTION_CORE = `${UI_EVENT_TYPE_ACTIONS}:${CATEGORY_CORE}`;
30 | export const EVENT_ACTION_CORE_STATE_MUTATION = `${EVENT_ACTION_CORE}:${ACTION_STATE_MUTATION}`;
31 | export const ACTION_STATE_MUTATION_STARTED = `${EVENT_ACTION_CORE_STATE_MUTATION}:${STATUS_STARTED}`;
32 | export const ACTION_STATE_MUTATION_FINISHED = `${EVENT_ACTION_CORE_STATE_MUTATION}:${STATUS_FINISHED}`;
33 | export const EVENT_ACTION_CORE_NAVIGATE = `${EVENT_ACTION_CORE}:${ACTION_NAVIGATE}`;
34 | export const ACTION_NAVIGATE_STARTED = `${EVENT_ACTION_CORE_NAVIGATE}:${STATUS_STARTED}`;
35 | export const ACTION_NAVIGATE_FINISHED = `${EVENT_ACTION_CORE_NAVIGATE}:${STATUS_FINISHED}`;
36 | export const EVENT_ACTION_DATASTORE = `${UI_EVENT_TYPE_ACTIONS}:${CATEGORY_DATASTORE}`;
37 | export const EVENT_ACTION_DATASTORE_CREATE = `${EVENT_ACTION_DATASTORE}:${ACTION_DATASTORE_CREATE}`;
38 | export const ACTION_DATASTORE_CREATE_STARTED = `${EVENT_ACTION_DATASTORE_CREATE}:${STATUS_STARTED}`;
39 | export const ACTION_DATASTORE_CREATE_FINISHED = `${EVENT_ACTION_DATASTORE_CREATE}:${STATUS_FINISHED}`;
40 | export const EVENT_ACTION_DATASTORE_DELETE = `${EVENT_ACTION_DATASTORE}:${ACTION_DATASTORE_DELETE}`;
41 | export const ACTION_DATASTORE_DELETE_STARTED = `${EVENT_ACTION_DATASTORE_DELETE}:${STATUS_STARTED}`;
42 | export const ACTION_DATASTORE_DELETE_FINISHED = `${EVENT_ACTION_DATASTORE_DELETE}:${STATUS_FINISHED}`;
43 | export const EVENT_ACTION_DATASTORE_UPDATE = `${EVENT_ACTION_DATASTORE}:${ACTION_DATASTORE_UPDATE}`;
44 | export const ACTION_DATASTORE_UPDATE_STARTED = `${EVENT_ACTION_DATASTORE_UPDATE}:${STATUS_STARTED}`;
45 | export const ACTION_DATASTORE_UPDATE_FINISHED = `${EVENT_ACTION_DATASTORE_UPDATE}:${STATUS_FINISHED}`;
46 | export const DATASTORE_QUERY_BY_ID_ERROR =
47 | "Error querying datastore item by id";
48 | export const AMPLIFY_SYMBOL =
49 | typeof Symbol !== "undefined" && typeof Symbol.for === "function"
50 | ? Symbol.for("amplify_default")
51 | : "@@amplify_default";
52 | export const useStateMutationAction = (initialState) => {
53 | const [state, setState] = React.useState(initialState);
54 | const setNewState = React.useCallback(
55 | (newState) => {
56 | const prevState = state;
57 | Hub.dispatch(
58 | UI_CHANNEL,
59 | {
60 | event: ACTION_STATE_MUTATION_STARTED,
61 | data: { prevState, newState },
62 | },
63 | EVENT_ACTION_CORE_STATE_MUTATION,
64 | AMPLIFY_SYMBOL
65 | );
66 | setState(newState);
67 | Hub.dispatch(
68 | UI_CHANNEL,
69 | {
70 | event: ACTION_STATE_MUTATION_FINISHED,
71 | data: { prevState, newState },
72 | },
73 | EVENT_ACTION_CORE_STATE_MUTATION,
74 | AMPLIFY_SYMBOL
75 | );
76 | },
77 | [state]
78 | );
79 | return [state, setNewState];
80 | };
81 | export const useNavigateAction = (options) => {
82 | const { type, url, anchor, target } = options;
83 | const run = React.useMemo(() => {
84 | switch (type) {
85 | case "url":
86 | return () => {
87 | window.open(url, target || "_self", "noopener noreferrer");
88 | };
89 | case "anchor":
90 | return () => {
91 | window.location.hash = anchor ?? "";
92 | };
93 | case "reload":
94 | return () => {
95 | window.location.reload();
96 | };
97 | default:
98 | return () => {
99 | // eslint-disable-next-line no-console
100 | console.warn(
101 | 'Please provide a valid navigate type. Available types are "url", "anchor" and "reload".'
102 | );
103 | };
104 | }
105 | }, [anchor, target, type, url]);
106 | const navigateAction = () => {
107 | Hub.dispatch(
108 | UI_CHANNEL,
109 | {
110 | event: ACTION_NAVIGATE_STARTED,
111 | data: options,
112 | },
113 | EVENT_ACTION_CORE_NAVIGATE,
114 | AMPLIFY_SYMBOL
115 | );
116 | run();
117 | Hub.dispatch(
118 | UI_CHANNEL,
119 | {
120 | event: ACTION_NAVIGATE_FINISHED,
121 | data: options,
122 | },
123 | EVENT_ACTION_CORE_NAVIGATE,
124 | AMPLIFY_SYMBOL
125 | );
126 | };
127 | return navigateAction;
128 | };
129 | export const findChildOverrides = (overrides, elementHierarchy) => {
130 | if (!overrides) {
131 | return null;
132 | }
133 | const filteredOverrides = Object.entries(overrides).filter((m) =>
134 | m[0].startsWith(elementHierarchy)
135 | );
136 | return Object.assign(
137 | {},
138 | ...Array.from(filteredOverrides, ([k, v]) => ({
139 | [k.replace(elementHierarchy, "")]: v,
140 | }))
141 | );
142 | };
143 | export const getOverrideProps = (overrides, elementHierarchy) => {
144 | if (!overrides) {
145 | return null;
146 | }
147 | const componentOverrides = Object.entries(overrides)
148 | .filter(([key]) => key === elementHierarchy)
149 | .flatMap(([, value]) => Object.entries(value))
150 | .filter((m) => m?.[0]);
151 | return Object.fromEntries(componentOverrides);
152 | };
153 | export function getOverridesFromVariants(variants, props) {
154 | const variantValueKeys = [
155 | ...new Set(
156 | variants.flatMap((variant) => Object.keys(variant.variantValues))
157 | ),
158 | ];
159 | const variantValuesFromProps = Object.keys(props)
160 | .filter((i) => variantValueKeys.includes(i) && props[i])
161 | .reduce((acc, key) => {
162 | return {
163 | ...acc,
164 | [key]: props[key],
165 | };
166 | }, {});
167 | const matchedVariants = variants.filter(({ variantValues }) => {
168 | return (
169 | Object.keys(variantValues).length ===
170 | Object.keys(variantValuesFromProps).length &&
171 | Object.entries(variantValues).every(
172 | ([key, value]) => variantValuesFromProps[key] === value
173 | )
174 | );
175 | });
176 | return matchedVariants.reduce((overrides, variant) => {
177 | return { ...overrides, ...variant.overrides };
178 | }, {});
179 | }
180 | export const mergeVariantsAndOverrides = (variants, overrides) => {
181 | if (!variants && !overrides) {
182 | return null;
183 | }
184 | if (!overrides) {
185 | return variants;
186 | }
187 | if (!variants) {
188 | return overrides;
189 | }
190 | const overrideKeys = new Set(Object.keys(overrides));
191 | const sharedKeys = Object.keys(variants).filter((variantKey) =>
192 | overrideKeys.has(variantKey)
193 | );
194 | const merged = Object.fromEntries(
195 | sharedKeys.map((sharedKey) => [
196 | sharedKey,
197 | { ...variants[sharedKey], ...overrides[sharedKey] },
198 | ])
199 | );
200 | return {
201 | ...variants,
202 | ...overrides,
203 | ...merged,
204 | };
205 | };
206 | export const isErrorWithMessage = (error) => {
207 | return (
208 | typeof error === "object" &&
209 | error !== null &&
210 | "message" in error &&
211 | typeof error.message === "string"
212 | );
213 | };
214 | export const toErrorWithMessage = (maybeError) => {
215 | if (isErrorWithMessage(maybeError)) return maybeError;
216 | try {
217 | return new Error(JSON.stringify(maybeError));
218 | } catch {
219 | return new Error(String(maybeError));
220 | }
221 | };
222 | export const getErrorMessage = (error) => {
223 | return toErrorWithMessage(error).message;
224 | };
225 | export const useTypeCastFields = ({ fields, modelName, schema }) => {
226 | return React.useMemo(() => {
227 | if (!schema) {
228 | return fields;
229 | }
230 | const castFields = {};
231 | Object.keys(fields).forEach((fieldName) => {
232 | const field = fields[fieldName];
233 | switch (schema?.models[modelName]?.fields?.[fieldName]?.type) {
234 | case "AWSTimestamp":
235 | castFields[fieldName] = Number(field);
236 | break;
237 | case "Boolean":
238 | castFields[fieldName] = Boolean(field);
239 | break;
240 | case "Int":
241 | castFields[fieldName] =
242 | typeof field === "string" ||
243 | (typeof field === "object" &&
244 | Object.prototype.toString.call(field) === "[object String]")
245 | ? parseInt(field)
246 | : field;
247 | break;
248 | case "Float":
249 | castFields[fieldName] = Number(field);
250 | break;
251 | default:
252 | castFields[fieldName] = field;
253 | break;
254 | }
255 | });
256 | return castFields;
257 | }, [fields, schema, modelName]);
258 | };
259 | export const useDataStoreCreateAction = ({
260 | model,
261 | fields: initialFields,
262 | schema,
263 | }) => {
264 | const fields = useTypeCastFields({
265 | fields: initialFields,
266 | modelName: model.name,
267 | schema,
268 | });
269 | return async () => {
270 | try {
271 | Hub.dispatch(
272 | UI_CHANNEL,
273 | {
274 | event: ACTION_DATASTORE_CREATE_STARTED,
275 | data: { fields },
276 | },
277 | EVENT_ACTION_DATASTORE_CREATE,
278 | AMPLIFY_SYMBOL
279 | );
280 | const item = await DataStore.save(new model(fields));
281 | Hub.dispatch(
282 | UI_CHANNEL,
283 | {
284 | event: ACTION_DATASTORE_CREATE_FINISHED,
285 | data: { fields, item },
286 | },
287 | EVENT_ACTION_DATASTORE_CREATE,
288 | AMPLIFY_SYMBOL
289 | );
290 | } catch (error) {
291 | Hub.dispatch(
292 | UI_CHANNEL,
293 | {
294 | event: ACTION_DATASTORE_CREATE_FINISHED,
295 | data: {
296 | fields,
297 | errorMessage: getErrorMessage(error),
298 | },
299 | },
300 | EVENT_ACTION_DATASTORE_CREATE,
301 | AMPLIFY_SYMBOL
302 | );
303 | }
304 | };
305 | };
306 | export const useDataStoreUpdateAction = ({
307 | fields: initialFields,
308 | id,
309 | model,
310 | schema,
311 | }) => {
312 | const fields = useTypeCastFields({
313 | fields: initialFields,
314 | modelName: model.name,
315 | schema,
316 | });
317 | return async () => {
318 | try {
319 | Hub.dispatch(
320 | UI_CHANNEL,
321 | {
322 | event: ACTION_DATASTORE_UPDATE_STARTED,
323 | data: { fields, id },
324 | },
325 | EVENT_ACTION_DATASTORE_UPDATE,
326 | AMPLIFY_SYMBOL
327 | );
328 | const original = await DataStore.query(model, id);
329 | if (!original) {
330 | throw new Error(`${DATASTORE_QUERY_BY_ID_ERROR}: ${id}`);
331 | }
332 | const item = await DataStore.save(
333 | model.copyOf(original, (updated) => {
334 | Object.assign(updated, fields);
335 | })
336 | );
337 | Hub.dispatch(
338 | UI_CHANNEL,
339 | {
340 | event: ACTION_DATASTORE_UPDATE_FINISHED,
341 | data: { fields, id, item },
342 | },
343 | EVENT_ACTION_DATASTORE_UPDATE,
344 | AMPLIFY_SYMBOL
345 | );
346 | } catch (error) {
347 | Hub.dispatch(
348 | UI_CHANNEL,
349 | {
350 | event: ACTION_DATASTORE_UPDATE_FINISHED,
351 | data: {
352 | fields,
353 | id,
354 | errorMessage: getErrorMessage(error),
355 | },
356 | },
357 | EVENT_ACTION_DATASTORE_UPDATE,
358 | AMPLIFY_SYMBOL
359 | );
360 | }
361 | };
362 | };
363 | export const useDataStoreDeleteAction =
364 | ({ model, id }) =>
365 | async () => {
366 | try {
367 | Hub.dispatch(
368 | UI_CHANNEL,
369 | {
370 | event: ACTION_DATASTORE_DELETE_STARTED,
371 | data: { id },
372 | },
373 | EVENT_ACTION_DATASTORE_DELETE,
374 | AMPLIFY_SYMBOL
375 | );
376 | await DataStore.delete(model, id);
377 | Hub.dispatch(
378 | UI_CHANNEL,
379 | {
380 | event: ACTION_DATASTORE_DELETE_FINISHED,
381 | data: { id },
382 | },
383 | EVENT_ACTION_DATASTORE_DELETE,
384 | AMPLIFY_SYMBOL
385 | );
386 | } catch (error) {
387 | Hub.dispatch(
388 | UI_CHANNEL,
389 | {
390 | event: ACTION_DATASTORE_DELETE_FINISHED,
391 | data: { id, errorMessage: getErrorMessage(error) },
392 | },
393 | EVENT_ACTION_DATASTORE_DELETE,
394 | AMPLIFY_SYMBOL
395 | );
396 | }
397 | };
398 | export const createDataStorePredicate = (predicateObject) => {
399 | const {
400 | and: groupAnd,
401 | or: groupOr,
402 | field,
403 | operator,
404 | operand,
405 | } = predicateObject;
406 | if (Array.isArray(groupAnd)) {
407 | const predicates = groupAnd.map((condition) =>
408 | createDataStorePredicate(condition)
409 | );
410 | return (p) =>
411 | p.and((model) => predicates.map((predicate) => predicate(model)));
412 | }
413 | if (Array.isArray(groupOr)) {
414 | const predicates = groupOr.map((condition) =>
415 | createDataStorePredicate(condition)
416 | );
417 | return (p) =>
418 | p.or((model) => predicates.map((predicate) => predicate(model)));
419 | }
420 | return (p) => {
421 | if (!!field && !!operator && p?.[field]?.[operator]) {
422 | return p[field][operator](operand);
423 | }
424 | return p;
425 | };
426 | };
427 | export const useDataStoreCollection = ({ model, criteria, pagination }) => {
428 | const [result, setResult] = React.useState({
429 | items: [],
430 | isLoading: false,
431 | error: undefined,
432 | });
433 | const fetch = () => {
434 | setResult({ isLoading: true, items: [] });
435 | const subscription = DataStore.observeQuery(
436 | model,
437 | criteria,
438 | pagination
439 | ).subscribe(
440 | (snapshot) => setResult({ items: snapshot.items, isLoading: false }),
441 | (error) => setResult({ items: [], error, isLoading: false })
442 | );
443 | if (subscription) {
444 | return () => subscription.unsubscribe();
445 | }
446 | };
447 | React.useEffect(fetch, []);
448 | return result;
449 | };
450 | export const useDataStoreItem = ({ model, id }) => {
451 | const [item, setItem] = React.useState();
452 | const [isLoading, setLoading] = React.useState(false);
453 | const [error, setError] = React.useState();
454 | const fetch = () => {
455 | setLoading(true);
456 | DataStore.query(model, id)
457 | .then(setItem)
458 | .catch(setError)
459 | .finally(() => setLoading(false));
460 | };
461 | React.useEffect(fetch, []);
462 | return {
463 | error,
464 | item,
465 | isLoading,
466 | };
467 | };
468 | export function useDataStoreBinding(props) {
469 | return props.type === "record"
470 | ? useDataStoreItem(props)
471 | : useDataStoreCollection(props);
472 | }
473 | export const useAuthSignOutAction = (options) => async () => {
474 | try {
475 | Hub.dispatch(
476 | UI_CHANNEL,
477 | {
478 | event: ACTION_AUTH_SIGNOUT_STARTED,
479 | data: { options },
480 | },
481 | EVENT_ACTION_AUTH_SIGNOUT,
482 | AMPLIFY_SYMBOL
483 | );
484 | await signOut(options);
485 | Hub.dispatch(
486 | UI_CHANNEL,
487 | {
488 | event: ACTION_AUTH_SIGNOUT_FINISHED,
489 | data: { options },
490 | },
491 | EVENT_ACTION_AUTH_SIGNOUT,
492 | AMPLIFY_SYMBOL
493 | );
494 | } catch (error) {
495 | Hub.dispatch(
496 | UI_CHANNEL,
497 | {
498 | event: ACTION_AUTH_SIGNOUT_FINISHED,
499 | data: { options, errorMessage: getErrorMessage(error) },
500 | },
501 | EVENT_ACTION_AUTH_SIGNOUT,
502 | AMPLIFY_SYMBOL
503 | );
504 | }
505 | };
506 | export const useAuth = () => {
507 | const [result, setResult] = React.useState({
508 | error: undefined,
509 | isLoading: true,
510 | user: undefined,
511 | });
512 | const fetchCurrentUserAttributes = React.useCallback(async () => {
513 | setResult((prevResult) => ({ ...prevResult, isLoading: true }));
514 | try {
515 | const attributes = await fetchUserAttributes();
516 | setResult({ user: { attributes }, isLoading: false });
517 | } catch (error) {
518 | setResult({ error, isLoading: false });
519 | }
520 | }, []);
521 | const handleAuth = React.useCallback(
522 | ({ payload }) => {
523 | switch (payload.event) {
524 | case "signedIn":
525 | case "signUp":
526 | case "tokenRefresh":
527 | case "autoSignIn": {
528 | fetchCurrentUserAttributes();
529 | break;
530 | }
531 | case "signedOut": {
532 | setResult({ user: undefined, isLoading: false });
533 | break;
534 | }
535 | case "tokenRefresh_failure":
536 | case "signIn_failure": {
537 | setResult({ error: payload.data, isLoading: false });
538 | break;
539 | }
540 | case "autoSignIn_failure": {
541 | setResult({ error: new Error(payload.message), isLoading: false });
542 | break;
543 | }
544 | default: {
545 | break;
546 | }
547 | }
548 | },
549 | [fetchCurrentUserAttributes]
550 | );
551 | React.useEffect(() => {
552 | const unsubscribe = Hub.listen("auth", handleAuth, "useAuth");
553 | fetchCurrentUserAttributes();
554 | return unsubscribe;
555 | }, [handleAuth, fetchCurrentUserAttributes]);
556 | return {
557 | ...result,
558 | };
559 | };
560 | export const validateField = (value, validations) => {
561 | for (const validation of validations) {
562 | if (value === undefined || value === "" || value === null) {
563 | if (validation.type === "Required") {
564 | return {
565 | hasError: true,
566 | errorMessage: validation.validationMessage || "The value is required",
567 | };
568 | } else {
569 | return {
570 | hasError: false,
571 | };
572 | }
573 | }
574 | const validationResult = checkValidation(value, validation);
575 | if (validationResult?.hasError) {
576 | return validationResult;
577 | }
578 | }
579 | return { hasError: false };
580 | };
581 | export const parseDateValidator = (dateValidator) => {
582 | const isTimestamp =
583 | `${parseInt(dateValidator)}`.length === dateValidator.length;
584 | return isTimestamp ? parseInt(dateValidator) : dateValidator;
585 | };
586 | const checkValidation = (value, validation) => {
587 | if (validation.numValues?.length) {
588 | switch (validation.type) {
589 | case "LessThanChar":
590 | return {
591 | hasError: !(value.length <= validation.numValues[0]),
592 | errorMessage:
593 | validation.validationMessage ||
594 | `The value must be shorter than ${validation.numValues[0]} characters`,
595 | };
596 | case "GreaterThanChar":
597 | return {
598 | hasError: !(value.length > validation.numValues[0]),
599 | errorMessage:
600 | validation.validationMessage ||
601 | `The value must be longer than ${validation.numValues[0]} characters`,
602 | };
603 | case "LessThanNum":
604 | return {
605 | hasError: !(value < validation.numValues[0]),
606 | errorMessage:
607 | validation.validationMessage ||
608 | `The value must be less than ${validation.numValues[0]}`,
609 | };
610 | case "GreaterThanNum":
611 | return {
612 | hasError: !(value > validation.numValues[0]),
613 | errorMessage:
614 | validation.validationMessage ||
615 | `The value must be greater than ${validation.numValues[0]}`,
616 | };
617 | case "EqualTo":
618 | return {
619 | hasError: !validation.numValues.some((el) => el === value),
620 | errorMessage:
621 | validation.validationMessage ||
622 | `The value must be equal to ${validation.numValues.join(" or ")}`,
623 | };
624 | default:
625 | }
626 | } else if (validation.strValues?.length) {
627 | switch (validation.type) {
628 | case "StartWith":
629 | return {
630 | hasError: !validation.strValues.some((el) => value.startsWith(el)),
631 | errorMessage:
632 | validation.validationMessage ||
633 | `The value must start with ${validation.strValues.join(", ")}`,
634 | };
635 | case "EndWith":
636 | return {
637 | hasError: !validation.strValues.some((el) => value.endsWith(el)),
638 | errorMessage:
639 | validation.validationMessage ||
640 | `The value must end with ${validation.strValues.join(", ")}`,
641 | };
642 | case "Contains":
643 | return {
644 | hasError: !validation.strValues.some((el) => value.includes(el)),
645 | errorMessage:
646 | validation.validationMessage ||
647 | `The value must contain ${validation.strValues.join(", ")}`,
648 | };
649 | case "NotContains":
650 | return {
651 | hasError: !validation.strValues.every((el) => !value.includes(el)),
652 | errorMessage:
653 | validation.validationMessage ||
654 | `The value must not contain ${validation.strValues.join(", ")}`,
655 | };
656 | case "BeAfter":
657 | return {
658 | hasError: !(
659 | new Date(value) >
660 | new Date(parseDateValidator(validation.strValues[0]))
661 | ),
662 | errorMessage:
663 | validation.validationMessage ||
664 | `The value must be after ${validation.strValues[0]}`,
665 | };
666 | case "BeBefore":
667 | return {
668 | hasError: !(
669 | new Date(value) <
670 | new Date(parseDateValidator(validation.strValues[0]))
671 | ),
672 | errorMessage:
673 | validation.validationMessage ||
674 | `The value must be before ${validation.strValues[0]}`,
675 | };
676 | }
677 | }
678 | switch (validation.type) {
679 | case "Email":
680 | const EMAIL_ADDRESS_REGEX =
681 | /^[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~](.?[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-*.?[a-zA-Z0-9])*.[a-zA-Z](-?[a-zA-Z0-9])+$/;
682 | return {
683 | hasError: !EMAIL_ADDRESS_REGEX.test(value),
684 | errorMessage:
685 | validation.validationMessage ||
686 | "The value must be a valid email address",
687 | };
688 | case "JSON":
689 | let isInvalidJSON = false;
690 | try {
691 | JSON.parse(value);
692 | } catch (e) {
693 | isInvalidJSON = true;
694 | }
695 | return {
696 | hasError: isInvalidJSON,
697 | errorMessage:
698 | validation.validationMessage ||
699 | "The value must be in a correct JSON format",
700 | };
701 | case "IpAddress":
702 | const IPV_4 =
703 | /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/;
704 | const IPV_6 =
705 | /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/;
706 | return {
707 | hasError: !(IPV_4.test(value) || IPV_6.test(value)),
708 | errorMessage:
709 | validation.validationMessage ||
710 | "The value must be an IPv4 or IPv6 address",
711 | };
712 | case "URL":
713 | let isInvalidUrl = false;
714 | try {
715 | new URL(value);
716 | } catch (e) {
717 | isInvalidUrl = true;
718 | }
719 | return {
720 | hasError: isInvalidUrl,
721 | errorMessage:
722 | validation.validationMessage ||
723 | "The value must be a valid URL that begins with a schema (i.e. http:// or mailto:)",
724 | };
725 | case "Phone":
726 | const PHONE = /^\+?\d[\d\s-]+$/;
727 | return {
728 | hasError: !PHONE.test(value),
729 | errorMessage:
730 | validation.validationMessage ||
731 | "The value must be a valid phone number",
732 | };
733 | default:
734 | }
735 | };
736 | const monthToShortMon = {
737 | "1": "Jan",
738 | "2": "Feb",
739 | "3": "Mar",
740 | "4": "Apr",
741 | "5": "May",
742 | "6": "Jun",
743 | "7": "Jul",
744 | "8": "Aug",
745 | "9": "Sep",
746 | "10": "Oct",
747 | "11": "Nov",
748 | "12": "Dec",
749 | };
750 | const invalidDateStr = "Invalid Date";
751 | export function formatDate(date, dateFormat) {
752 | if (date === undefined || date === null) {
753 | return date;
754 | }
755 | const validDate = new Date(Date.parse(date));
756 | if (validDate.toString() === invalidDateStr) {
757 | return date;
758 | }
759 | const splitDate = date.split(/-|\+|Z/);
760 | const year = splitDate[0];
761 | const month = splitDate[1];
762 | const day = splitDate[2];
763 | const truncatedMonth = month.replace(/^0+/, "");
764 | switch (dateFormat) {
765 | case "locale":
766 | return validDate.toLocaleDateString();
767 | case "YYYY.MM.DD":
768 | return `${year}.${month}.${day}`;
769 | case "DD.MM.YYYY":
770 | return `${day}.${month}.${year}`;
771 | case "MM/DD/YYYY":
772 | return `${month}/${day}/${year}`;
773 | case "Mmm DD, YYYY":
774 | return `${monthToShortMon[truncatedMonth]} ${day}, ${year}`;
775 | default:
776 | return date;
777 | }
778 | }
779 | export function formatTime(time, timeFormat) {
780 | if (time === undefined || time === null) {
781 | return time;
782 | }
783 | const splitTime = time.split(/:|Z/);
784 | if (splitTime.length < 3) {
785 | return time;
786 | }
787 | const validTime = new Date();
788 | validTime.setHours(Number.parseInt(splitTime[0], 10));
789 | validTime.setMinutes(Number.parseInt(splitTime[1], 10));
790 | const splitSeconds = splitTime[2].split(".");
791 | validTime.setSeconds(
792 | Number.parseInt(splitSeconds[0], 10),
793 | Number.parseInt(splitSeconds[1], 10)
794 | );
795 | if (validTime.toString() === invalidDateStr) {
796 | return time;
797 | }
798 | switch (timeFormat) {
799 | case "locale":
800 | return validTime.toLocaleTimeString();
801 | case "hours24":
802 | return validTime.toLocaleTimeString("en-gb");
803 | case "hours12":
804 | return validTime.toLocaleTimeString("en-us");
805 | default:
806 | return time;
807 | }
808 | }
809 | export function formatDateTime(dateTimeStr, dateTimeFormat) {
810 | if (dateTimeStr === undefined || dateTimeStr === null) {
811 | return dateTimeStr;
812 | }
813 | const dateTime = /^\d+$/.test(dateTimeStr)
814 | ? new Date(Number.parseInt(dateTimeStr, 10))
815 | : new Date(Date.parse(dateTimeStr));
816 | if (dateTime.toString() === invalidDateStr) {
817 | return dateTimeStr;
818 | }
819 | if (dateTimeFormat === "locale") {
820 | return dateTime.toLocaleString();
821 | }
822 | const dateAndTime = dateTime.toISOString().split("T");
823 | const date = formatDate(dateAndTime[0], dateTimeFormat.dateFormat);
824 | const time = formatTime(dateAndTime[1], dateTimeFormat.timeFormat);
825 | return `${date} - ${time}`;
826 | }
827 | export function formatter(value, formatterInput) {
828 | switch (formatterInput.type) {
829 | case "DateFormat":
830 | return formatDate(value, formatterInput.format);
831 | case "DateTimeFormat":
832 | return formatDateTime(value, formatterInput.format);
833 | case "TimeFormat":
834 | return formatTime(value, formatterInput.format);
835 | default:
836 | return value;
837 | }
838 | }
839 | export const fetchByPath = (input, path, accumlator = []) => {
840 | const currentPath = path.split(".");
841 | const head = currentPath.shift();
842 | if (input && head && input[head] !== undefined) {
843 | if (!currentPath.length) {
844 | accumlator.push(input[head]);
845 | } else {
846 | fetchByPath(input[head], currentPath.join("."), accumlator);
847 | }
848 | }
849 | return accumlator[0];
850 | };
851 | export const processFile = async ({ file }) => {
852 | const fileExtension = file.name.split(".").pop();
853 | return file
854 | .arrayBuffer()
855 | .then((filebuffer) => window.crypto.subtle.digest("SHA-1", filebuffer))
856 | .then((hashBuffer) => {
857 | const hashArray = Array.from(new Uint8Array(hashBuffer));
858 | const hashHex = hashArray
859 | .map((a) => a.toString(16).padStart(2, "0"))
860 | .join("");
861 | return { file, key: `${hashHex}.${fileExtension}` };
862 | });
863 | };
864 |
--------------------------------------------------------------------------------