├── .dockerignore ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .husky ├── pre-commit └── pre-push ├── .prettierignore ├── .prettierrc ├── LICENSE ├── Makefile ├── README.md ├── __tests__ ├── api │ ├── parse-html.test.ts │ ├── replacePlaceholdersDuringStreaming.test.ts │ └── streamResponseToUser.test.ts ├── apiMock │ ├── chunksToProperties.test.ts │ ├── getMockedProperties.test.ts │ ├── removePropertiesItems.test.ts │ └── slug.test.ts ├── lib │ ├── actionUtils.test.ts │ ├── edge-runtime │ │ ├── angelaUtils.test.ts │ │ ├── apiResponseSimplification.test.ts │ │ ├── dataAnalysis.test.ts │ │ ├── removeOldestFunctionCalls.test.ts │ │ └── utils.test.ts │ ├── embed-docs │ │ ├── getUrlText.test.ts │ │ ├── splitIntoTextChunks.test.ts │ │ └── utils.test.ts │ ├── parsers │ │ ├── parseDataAnalysisResponse.test.ts │ │ └── parsers.test.ts │ ├── v2 │ │ ├── edge-runtime │ │ │ ├── addAssistantHistory.test.ts │ │ │ ├── ai.test.ts │ │ │ ├── dataAnalysis.test.ts │ │ │ └── filter-actions.test.ts │ │ └── prompts │ │ │ ├── clarification.test.ts │ │ │ ├── dataAnalysis.test.ts │ │ │ ├── parseGeneratedCode.test.ts │ │ │ ├── routing.test.ts │ │ │ ├── summariseChatHistory.test.ts │ │ │ └── utils.test.ts │ └── v3 │ │ ├── prompts_parsers │ │ ├── explanation.test.ts │ │ └── matching.test.ts │ │ └── utils.test.ts ├── missingParamCorrect.test.ts ├── postprocessResponse.test.ts ├── prompts │ ├── analysisPrompt.test.ts │ ├── apiMockPrompt.test.ts │ ├── getActionDescriptions.test.ts │ ├── reqBodyToTS.test.ts │ ├── requestCorrectionPrompt.test.ts │ ├── suggestFollowUps.test.ts │ ├── summarizeText.test.ts │ └── testData.ts ├── requests.test.ts ├── swagger-to-actions.test.ts ├── testActions.ts ├── testData │ ├── Henry-Pulver-CV.pdf │ ├── LOA_superflows-sign-in_413.pdf │ ├── jira-openapi-spec.json │ ├── linuxReddit.json │ ├── localtest-openapi-spec.json │ ├── openai-openapi-spec.json │ ├── pokemon.json │ ├── posthog-openapi-spec.json │ ├── sentry-openapi-spec.json │ ├── sf2.json │ ├── supabase-openapi-spec.json │ └── superflows-openapi.json └── utils │ ├── chunkString.test.ts │ ├── isDate.test.ts │ ├── isID.test.ts │ ├── isURL.test.ts │ ├── joinArraysNoDuplicates.test.ts │ ├── jsonSplitter.test.ts │ └── stripTrailingAndCurly.test.ts ├── components ├── actions │ ├── APITabs.tsx │ ├── actionsSection.tsx │ ├── consts.ts │ ├── editActionModal.tsx │ ├── editActionTagModal.tsx │ ├── uploadModal.tsx │ └── viewPromptModal.tsx ├── approval │ ├── addQuestionModal.tsx │ ├── editModals.tsx │ ├── followUps.tsx │ ├── question.tsx │ ├── types.ts │ └── verifyAnswerScreen.tsx ├── autoGrowingTextarea.tsx ├── checkbox.tsx ├── combobox.tsx ├── contextManagers │ └── profile.tsx ├── dropdown.tsx ├── floatingLabelInput.tsx ├── flyout.tsx ├── flyoutMenu.tsx ├── getServerSideProps.ts ├── headers.tsx ├── icons.tsx ├── loadingspinner.tsx ├── modal.tsx ├── navbar.tsx ├── onboarding │ ├── confirmAuth.tsx │ ├── progressBar.tsx │ └── uploadSpec.tsx ├── paginationPageSelector.tsx ├── playground.tsx ├── playgroundChatbot.tsx ├── selectBox.tsx ├── signIn.tsx ├── toggle.tsx ├── transcripts │ └── transcriptSearchSidebar.tsx └── warningModal.tsx ├── docker └── development │ ├── Dockerfile │ ├── README.md │ ├── docker-compose.yml │ ├── init-windows.ps1 │ └── init.sh ├── global-jest-setup.ts ├── jest.config.js ├── lib ├── actionUtils.ts ├── apiKey.ts ├── builtinActions.ts ├── consts.ts ├── database.types.ts ├── edge-runtime │ ├── ai.ts │ ├── angelaUtils.ts │ ├── apiResponseSimplification.ts │ ├── dataAnalysis.ts │ ├── filterActions.ts │ ├── llmResponseCache.ts │ ├── missingParamCorrection.ts │ ├── requests.ts │ ├── summarize.ts │ └── utils.ts ├── embed-docs │ ├── docsSearch.ts │ ├── embedDocs.ts │ ├── embedText.ts │ ├── getNestedUrls.ts │ └── utils.ts ├── funLoadingMessages.ts ├── hooks │ └── useDocumentsLoader.ts ├── language.ts ├── models.ts ├── parsers │ ├── dataAnalysis.ts │ └── parsers.ts ├── prompts │ ├── actionFiltering.ts │ ├── apiMock.ts │ ├── chatBot.ts │ ├── dataAnalysis.ts │ ├── hallucinateDocs.ts │ ├── requestCorrection.ts │ ├── suggestFollowUps.ts │ ├── summarizeText.ts │ └── tsConversion.ts ├── queryLLM.ts ├── types.ts ├── utils.ts ├── v2 │ ├── builtinActions.ts │ ├── edge-runtime │ │ ├── ai.ts │ │ ├── clarification.ts │ │ ├── dataAnalysis.ts │ │ ├── filterActions.ts │ │ ├── summariseChatHistory.ts │ │ └── utils.ts │ └── prompts │ │ ├── actionFiltering.ts │ │ ├── chatBot.ts │ │ ├── clarification.ts │ │ ├── dataAnalysis.ts │ │ ├── explainNotPossible.ts │ │ ├── routing.ts │ │ ├── summariseChatHistory.ts │ │ ├── utils.ts │ │ └── writeActionDescription.ts └── v3 │ ├── edge-runtime │ ├── ai.ts │ ├── dataAnalysis.ts │ └── matching.ts │ ├── prompts_parsers │ ├── chatToDocs.ts │ ├── codeGen.ts │ ├── descriptionGeneration.ts │ ├── explanation.ts │ ├── filtering.ts │ ├── generateAlternativeQuestions.ts │ ├── matching.ts │ ├── routing.ts │ └── utils.ts │ └── utils.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _error.tsx ├── actions.tsx ├── ai-settings.tsx ├── analytics.tsx ├── api-settings.tsx ├── api │ ├── create-org.ts │ ├── embed-questions.ts │ ├── embed-text.ts │ ├── generate-alternative-questions.ts │ ├── join-org.ts │ ├── mock │ │ └── [...slug].ts │ ├── parse-html.ts │ ├── search-docs.ts │ ├── swagger-to-actions.ts │ ├── team.ts │ ├── v1 │ │ ├── answers.ts │ │ ├── confirm.ts │ │ ├── feedback.ts │ │ └── follow-ups.ts │ ├── v3 │ │ ├── generate-answer-description.ts │ │ └── generate-answer-offline.ts │ └── write-action-descriptions.ts ├── approval │ ├── [id].tsx │ └── index.tsx ├── chat-to-docs.tsx ├── index.tsx ├── onboarding.tsx ├── project.tsx ├── sign-in.tsx ├── team.tsx └── usage.tsx ├── postcss.config.js ├── public ├── crm-ai.png ├── favicon.png ├── google.svg ├── modal.png ├── sf-actions-corner.png ├── sf-actions.png ├── sf-crm-ai-corner.png ├── sf-logo-long.png ├── superflows-in-action.gif └── superflows-sidebar.gif ├── sentry.client.config.ts ├── sentry.edge.config.ts ├── sentry.server.config.ts ├── styles └── globals.css ├── supabase ├── .gitignore ├── config.toml ├── functions │ ├── execute-code-2 │ │ ├── actionUtils.ts │ │ ├── index.ts │ │ ├── requests.ts │ │ ├── types.ts │ │ └── utils.ts │ └── execute-code │ │ └── index.ts ├── migrations │ ├── 20230619150835_initialize_tables.sql │ ├── 20230627202036_tweak-orgs.sql │ ├── 20230703134143_add-usage-table.sql │ ├── 20230704155118_usage-limit-free-tier.sql │ ├── 20230705131234_add-endpoint-postprocessing.sql │ ├── 20230713135915_groups-to-tags.sql │ ├── 20230718120522_support-auth-methods.sql │ ├── 20230719101013_email-password-login.sql │ ├── 20230720103603_reply-in-same-language.sql │ ├── 20230721175829_add-team-page.sql │ ├── 20230724092452_enable-preset-api-specs.sql │ ├── 20230801125929_add_num_user_queries.sql │ ├── 20230811160337_enable-multiple-apis-per-project.sql │ ├── 20230824170129_add-summaries-to-chat-messages.sql │ ├── 20230905191132_add-finetuned-models.sql │ ├── 20230908092648_nonunique-finetuned-models.sql │ ├── 20230911140113_allow-setting-llm.sql │ ├── 20230913160910_add-fixed-headers.sql │ ├── 20230913183248_add_feedback_table.sql │ ├── 20230927100441_allow-query-param-auth.sql │ ├── 20230929150231_sanitize-urls-or-ids-first.sql │ ├── 20230929160459_enable-disable-confirmation-per-action.sql │ ├── 20231010172954_enable-setting-language.sql │ ├── 20231015163802_add-embedded-docs-table.sql │ ├── 20231024124328_add-chatbot-instructions.sql │ ├── 20231025130024_add-action-links.sql │ ├── 20231031180334_drop-auth-header-constraint.sql │ ├── 20231101022950_add-doc_chunks-queries.sql │ ├── 20231129102947_add-analytics.sql │ ├── 20231214175242_add-caching-feature-flag.sql │ ├── 20231219180935_caching-2.sql │ ├── 20240111123041_bertie-ai-v2.sql │ ├── 20240304093715_cascade-from-conversations.sql │ ├── 20240314095402_auditable_logs.sql │ ├── 20240316165535_caching_bertie.sql │ ├── 20240411111508_fun-loading-messages.sql │ ├── 20240415100158_add-data-analysis-flag.sql │ ├── 20240416162029_cassius.sql │ ├── 20240422173420_fix-issue-in-functions.sql │ ├── 20240422200717_fix-issue-in-functions-2.sql │ ├── 20240512202947_add-disable-direct-flag.sql │ ├── 20240515124621_add-analytics-page.sql │ ├── 20240518213300_approval-column.sql │ ├── 20240520165609_unique-fnNames.sql │ ├── 20240527132119_cassius-revamped-docs.sql │ ├── 20240605105600_store-variables-for-cache-correctly.sql │ ├── 20240606135049_add-user-id.sql │ └── 20240613150006_fix_chat_to_docs_page.sql └── seed.sql ├── tailwind.config.js ├── tsconfig.json └── update-types.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | docker/* 3 | .git -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Supabase 2 | NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000 3 | NEXT_PUBLIC_SUPABASE_ANON_KEY= 4 | SERVICE_LEVEL_KEY_SUPABASE= 5 | SUPERFLOWS_PORT=8080 6 | POSTGRES_USER=postgres 7 | # AI 8 | OPENAI_API_KEY= 9 | 10 | # (Optional) Google 11 | GOOGLE_APP_CLIENT_ID= 12 | GOOGLE_APP_CLIENT_SECRET= 13 | 14 | # (Optional) Sentry 15 | SENTRY_AUTH_TOKEN= 16 | NEXT_PUBLIC_SENTRY_DSN= 17 | SENTRY_PROJECT= 18 | SENTRY_ORG= 19 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "@next/next/no-html-link-for-pages": "off" 5 | }, 6 | "ignorePatterns": ["docker/development/supabase"] 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # Sentry Auth Token 133 | .sentryclirc 134 | 135 | # Custom 136 | .vscode/ 137 | local/ 138 | .idea/ 139 | install-sidebar.sh 140 | .vercel 141 | docker/development/supabase/ 142 | # Sentry Config File 143 | .sentryclirc 144 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | npx pretty-quick --staged --fix 6 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run testprepush 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | .next 3 | node_modules/ 4 | tsconfig.json 5 | tailwind.config.js 6 | .eslintrc.json 7 | next.config.js 8 | next-env.d.ts 9 | package.json 10 | postcss.config.js 11 | README.md 12 | sentry.* 13 | styles/globals.css 14 | supabase/.temp/ 15 | __tests__/testData/ 16 | docker/development/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all" 3 | } 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build clean 2 | 3 | p: 4 | npx prettier --write . --check '!./docker/development/supabase/docker/volumes' 5 | 6 | pc: p 7 | npm run lint 8 | 9 | run: 10 | npm run dev 11 | 12 | types: 13 | ./update-types.sh 14 | 15 | build: 16 | npm run build 17 | 18 | test: 19 | npm test 20 | -------------------------------------------------------------------------------- /__tests__/api/parse-html.test.ts: -------------------------------------------------------------------------------- 1 | import { removeHiddenElements } from "../../pages/api/parse-html"; 2 | import * as cheerio from "cheerio"; 3 | 4 | describe("removeHiddenElements", () => { 5 | const allversions = [ 6 | 'style="display:none"', 7 | 'style="display: none"', 8 | 'style="display:none;"', 9 | 'style="display: none;"', 10 | "style='display:none'", 11 | "style='display: none'", 12 | "style='display:none;'", 13 | "style='display: none;'", 14 | ]; 15 | it("should remove hidden elements", () => { 16 | allversions.forEach((style) => { 17 | const cheerioInput = cheerio 18 | .load( 19 | `
Something that's visible
`, 20 | ) 21 | .root() 22 | .find("body"); 23 | const out = removeHiddenElements(cheerioInput); 24 | expect(out.html()).toEqual("
Something that's visible
"); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /__tests__/api/streamResponseToUser.test.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from "stream"; 2 | import { streamResponseToUser } from "../../lib/edge-runtime/angelaUtils"; 3 | 4 | describe("streamResponseToUser", () => { 5 | it("very simple", async () => { 6 | const mockReadable = new Readable(); 7 | mockReadable.push('data: {"choices": [{"delta": {"content": "hello"}}]}'); 8 | mockReadable.push(null); 9 | 10 | const uint8array = new TextEncoder().encode(mockReadable.read()); 11 | const mockStream = new ReadableStream({ 12 | start(controller) { 13 | controller.enqueue(uint8array); 14 | controller.close(); 15 | }, 16 | }); 17 | const mockFn = jest.fn(); 18 | // @ts-ignore 19 | const out = await streamResponseToUser(mockStream, mockFn, {}); 20 | expect(out).toEqual("hello"); 21 | expect(mockFn).toBeCalledWith({ role: "assistant", content: "hello" }); 22 | }); 23 | it("replace ID1 with original id correctly", async () => { 24 | const mockReadable = new Readable(); 25 | mockReadable.push( 26 | 'data: {"choices": [{"delta": {"content": "The id is"}}]}', 27 | ); 28 | mockReadable.push('data: {"choices": [{"delta": {"content": " ID"}}]}'); 29 | mockReadable.push('data: {"choices": [{"delta": {"content": "1 "}}]}'); 30 | mockReadable.push(null); // end the stream 31 | 32 | const uint8array = new TextEncoder().encode(mockReadable.read()); 33 | const mockStream = new ReadableStream({ 34 | start(controller) { 35 | controller.enqueue(uint8array); 36 | controller.close(); 37 | }, 38 | }); 39 | const mockFn = jest.fn(); 40 | // @ts-ignore 41 | const out = await streamResponseToUser(mockStream, mockFn, { 42 | "53fa4-3f3f3-3f3f3-3f3f3-3f3f3": "ID1", 43 | }); 44 | expect(out).toEqual("The id is ID1 "); 45 | expect(mockFn).toBeCalledWith({ role: "assistant", content: "The id is" }); 46 | expect(mockFn).toBeCalledWith({ 47 | role: "assistant", 48 | content: " 53fa4-3f3f3-3f3f3-3f3f3-3f3f3 ", 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /__tests__/apiMock/getMockedProperties.test.ts: -------------------------------------------------------------------------------- 1 | import "jest"; 2 | import { getLLMResponse } from "../../lib/queryLLM"; 3 | import { getMockedProperties } from "../../pages/api/mock/[...slug]"; 4 | 5 | jest.mock("../../lib/queryLLM"); 6 | 7 | // This resets the number of times called counts between tests 8 | afterEach(() => { 9 | jest.clearAllMocks(); 10 | }); 11 | describe("getMockedProperties", () => { 12 | it("simple test case", async () => { 13 | const openAiResponse = ` 14 | firstName: John 15 | lastName: Smith 16 | `; 17 | 18 | const openApiProperties = { 19 | firstName: { 20 | type: "string", 21 | nullable: false, 22 | }, 23 | lastName: { 24 | type: "string", 25 | nullable: false, 26 | }, 27 | }; 28 | 29 | (getLLMResponse as jest.Mock).mockReturnValue(openAiResponse); 30 | 31 | const res = await getMockedProperties( 32 | openApiProperties, 33 | "/api/v1/Status/fullname/123", 34 | "GET", 35 | [ 36 | { 37 | path: ["id"], 38 | data: "The user's id", 39 | }, 40 | ], 41 | ); 42 | expect(res).toEqual({ firstName: "John", lastName: "Smith" }); 43 | }); 44 | 45 | it("with array", async () => { 46 | const openApiProperties = { 47 | id: { 48 | type: "string", 49 | }, 50 | name: { 51 | type: "integer", 52 | }, 53 | }; 54 | 55 | const openAiResponse = ` 56 | id: [1,2,3] 57 | name: ['John', 'Eric', 'Martin'] 58 | `; 59 | 60 | const expected = [ 61 | { 62 | id: 1, 63 | name: "John", 64 | }, 65 | { 66 | id: 2, 67 | name: "Eric", 68 | }, 69 | { 70 | id: 3, 71 | name: "Martin", 72 | }, 73 | ]; 74 | 75 | (getLLMResponse as jest.Mock).mockReturnValue(openAiResponse); 76 | 77 | const res = await getMockedProperties( 78 | openApiProperties, 79 | "/api/v1/Status/fullname/123", 80 | "GET", 81 | null, 82 | undefined, 83 | true, 84 | ); 85 | expect(res).toEqual(expected); 86 | }); 87 | it("nested object", async () => { 88 | // TODO: remove properties from here 89 | // learney.atlassian.net/browse/SF-2007 90 | const openAiResponse = ` 91 | browserSdkVersion: 1 92 | dateCreated: 2021-01-01 93 | dsn.properties.cdn: nice-cdn 94 | dsn.properties.csp: nice-csp 95 | `; 96 | 97 | const properties = { 98 | browserSdkVersion: { 99 | type: "string", 100 | }, 101 | dateCreated: { 102 | type: "string", 103 | }, 104 | dsn: { 105 | type: "object", 106 | properties: { 107 | cdn: { 108 | type: "string", 109 | }, 110 | csp: { 111 | type: "string", 112 | }, 113 | }, 114 | }, 115 | }; 116 | 117 | (getLLMResponse as jest.Mock).mockReturnValue(openAiResponse); 118 | 119 | const expectedOutput = { 120 | browserSdkVersion: 1, 121 | dateCreated: "2021-01-01", 122 | dsn: { cdn: "nice-cdn", csp: "nice-csp" }, 123 | }; 124 | 125 | const res = await getMockedProperties( 126 | properties, 127 | "/api/v1/Status/fullname/123", 128 | "GET", 129 | [ 130 | { 131 | path: ["id"], 132 | data: "The user's id", 133 | }, 134 | ], 135 | ); 136 | 137 | expect(res).toEqual(expectedOutput); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /__tests__/apiMock/removePropertiesItems.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "@jest/globals"; 2 | import { removePropertiesItems } from "../../pages/api/mock/[...slug]"; 3 | 4 | describe("removePropertiesItems", () => { 5 | const expected = { out: 1, nowt: 2 }; 6 | it("does nothing to nice output", () => { 7 | const out = removePropertiesItems(expected); 8 | expect(out).toEqual(expected); 9 | }); 10 | it("nested in properties", () => { 11 | const out = removePropertiesItems({ properties: expected }); 12 | expect(out).toEqual(expected); 13 | }); 14 | it("nested in items", () => { 15 | const out = removePropertiesItems({ items: expected }); 16 | expect(out).toEqual(expected); 17 | }); 18 | it("double nested", () => { 19 | const out = removePropertiesItems({ properties: { items: expected } }); 20 | expect(out).toEqual(expected); 21 | }); 22 | it("double nested with 1 other entry", () => { 23 | const out = removePropertiesItems({ 24 | properties: { items: expected }, 25 | another: "one", 26 | }); 27 | expect(out).toEqual({ ...expected, another: "one" }); 28 | }); 29 | it("double nested with multiple other entries", () => { 30 | const out = removePropertiesItems({ 31 | properties: { items: expected, andAnother: "one" }, 32 | another: "one", 33 | }); 34 | expect(out).toEqual({ ...expected, another: "one", andAnother: "one" }); 35 | }); 36 | it("double nested with inner non-removed key", () => { 37 | const out = removePropertiesItems({ 38 | properties: { key: expected, andAnother: "one" }, 39 | another: "one", 40 | }); 41 | expect(out).toEqual({ key: expected, another: "one", andAnother: "one" }); 42 | }); 43 | it("double nested with outer non-removed key", () => { 44 | const out = removePropertiesItems({ 45 | key: { items: expected, andAnother: "one" }, 46 | another: "one", 47 | }); 48 | expect(out).toEqual({ 49 | key: { ...expected, andAnother: "one" }, 50 | another: "one", 51 | }); 52 | }); 53 | it("array of objects", () => { 54 | const out = removePropertiesItems([ 55 | { items: expected }, 56 | { items: expected }, 57 | { items: expected }, 58 | ]); 59 | expect(out).toEqual([expected, expected, expected]); 60 | }); 61 | it("array of double-nested objects", () => { 62 | const out = removePropertiesItems([ 63 | { items: { properties: expected } }, 64 | { items: { properties: expected } }, 65 | { items: { properties: expected } }, 66 | ]); 67 | expect(out).toEqual([expected, expected, expected]); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /__tests__/lib/embed-docs/getUrlText.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isTextWithSubstance, 3 | splitTextByHeaders, 4 | } from "../../../lib/embed-docs/utils"; 5 | 6 | describe("markdownToObject", () => { 7 | it("Simple", () => { 8 | let markdownText = ` 9 | # Heading 1 10 | This is some text below heading 1. 11 | ## Heading 2 12 | This is some text below heading 2. 13 | This is more text below heading 2. 14 | ## Heading 2.1 15 | This is some text below heading 2.1. 16 | # Heading 3 17 | This is some text below heading 3. 18 | `; 19 | const out = splitTextByHeaders(markdownText); 20 | expect(out).toEqual({ 21 | "Heading 1": "This is some text below heading 1.", 22 | "Heading 2": 23 | "This is some text below heading 2.\nThis is more text below heading 2.", 24 | "Heading 2.1": "This is some text below heading 2.1.", 25 | "Heading 3": "This is some text below heading 3.", 26 | }); 27 | }); 28 | it("No text below top heading", () => { 29 | let markdownText = ` 30 | # Heading 1 31 | 32 | ## Heading 2 33 | This is some text below heading 2. 34 | This is more text below heading 2. 35 | ## Heading 2.1 36 | This is some text below heading 2.1. 37 | # Heading 3 38 | This is some text below heading 3. 39 | `; 40 | const out = splitTextByHeaders(markdownText); 41 | expect(out).toEqual({ 42 | "Heading 1": "", 43 | "Heading 2": 44 | "This is some text below heading 2.\nThis is more text below heading 2.", 45 | "Heading 2.1": "This is some text below heading 2.1.", 46 | "Heading 3": "This is some text below heading 3.", 47 | }); 48 | }); 49 | it("Not starting with a heading", () => { 50 | let markdownText = ` 51 | This is some text at the top. 52 | ## Heading 2 53 | This is some text below heading 2. 54 | This is more text below heading 2. 55 | ## Heading 2.1 56 | This is some text below heading 2.1. 57 | # Heading 3 58 | This is some text below heading 3. 59 | `; 60 | const out = splitTextByHeaders(markdownText); 61 | expect(out).toEqual({ 62 | "": "This is some text at the top.", 63 | "Heading 2": 64 | "This is some text below heading 2.\nThis is more text below heading 2.", 65 | "Heading 2.1": "This is some text below heading 2.1.", 66 | "Heading 3": "This is some text below heading 3.", 67 | }); 68 | }); 69 | it("Includes a heading with newline before text", () => { 70 | let markdownText = ` 71 | This is some text at the top. 72 | ## Heading 2 73 | This is some text below heading 2. 74 | This is more text below heading 2. 75 | ## 76 | Heading 2.1 77 | This is some text below heading 2.1. 78 | # Heading 3 79 | This is some text below heading 3. 80 | `; 81 | const out = splitTextByHeaders(markdownText); 82 | expect(out).toEqual({ 83 | "": "This is some text at the top.", 84 | "Heading 2": 85 | "This is some text below heading 2.\nThis is more text below heading 2.", 86 | "Heading 2.1": "This is some text below heading 2.1.", 87 | "Heading 3": "This is some text below heading 3.", 88 | }); 89 | }); 90 | }); 91 | 92 | describe("isTextWithSubstance", () => { 93 | it("Simple", () => { 94 | expect(isTextWithSubstance("")).toEqual(false); 95 | }); 96 | it("", () => { 97 | const text = 98 | "* Invítanos con el correo [integrations@woffu.com](mailto:integrations@woffu.com) a tu cuenta de a3innuva Nómina para vincularla con tu cuenta de Woffu."; 99 | expect(isTextWithSubstance(text)).toEqual(true); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /__tests__/lib/parsers/parseDataAnalysisResponse.test.ts: -------------------------------------------------------------------------------- 1 | import { parseDataAnalysisResponse } from "../../../lib/parsers/dataAnalysis"; 2 | 3 | describe("parseDataAnalysisResponse", () => { 4 | it("basic", () => { 5 | const code = parseDataAnalysisResponse(`Thoughts: I am. I think. I will. 6 | 7 | Code: 8 | \`\`\` 9 | var graphData = { 10 | graphTitle: "title", 11 | type: "line", 12 | data: [ 13 | { x: 1, y: 2 }, 14 | { x: 2, y: 3 }, 15 | ], 16 | xLabel: "x", 17 | yLabel: "y", 18 | }; 19 | \`\`\``); 20 | expect(code).toEqual({ 21 | code: `var graphData = { 22 | graphTitle: "title", 23 | type: "line", 24 | data: [ 25 | { x: 1, y: 2 }, 26 | { x: 2, y: 3 }, 27 | ], 28 | xLabel: "x", 29 | yLabel: "y", 30 | };`, 31 | }); 32 | }); 33 | it("all options for end of ``` line", () => { 34 | const options = ["js", "javascript", "ts", "typescript"]; 35 | for (const option of options) { 36 | const code = parseDataAnalysisResponse(`Thoughts: I am. I think. I will. 37 | 38 | Code: 39 | \`\`\`${option} 40 | var graphData = { 41 | graphTitle: "title", 42 | type: "line", 43 | data: [ 44 | { x: 1, y: 2 }, 45 | { x: 2, y: 3 }, 46 | ], 47 | xLabel: "x", 48 | yLabel: "y", 49 | }; 50 | \`\`\``); 51 | expect(code).toEqual({ 52 | code: `var graphData = { 53 | graphTitle: "title", 54 | type: "line", 55 | data: [ 56 | { x: 1, y: 2 }, 57 | { x: 2, y: 3 }, 58 | ], 59 | xLabel: "x", 60 | yLabel: "y", 61 | };`, 62 | }); 63 | } 64 | }); 65 | it("no code", () => { 66 | const code = parseDataAnalysisResponse(`Thoughts: I am. I think. I will.`); 67 | expect(code).toEqual(null); 68 | }); 69 | it("includes fetch()", () => { 70 | const code = parseDataAnalysisResponse(`Thoughts: I am. I think. I will. 71 | 72 | Code: 73 | \`\`\` 74 | fetch("https://some-url.com") 75 | \`\`\``); 76 | expect(code).toEqual(null); 77 | }); 78 | it("graphData not defined", () => { 79 | const code = parseDataAnalysisResponse(`Thoughts: I am. I think. I will. 80 | 81 | Code: 82 | \`\`\` 83 | let nonIllegalCode = "123"; 84 | \`\`\``); 85 | expect(code).toEqual(null); 86 | }); 87 | it("functionOutput redefined", () => { 88 | const code = parseDataAnalysisResponse( 89 | `Thoughts: I am. I think. I will. 90 | 91 | Code: 92 | \`\`\` 93 | let graphData = "123"; 94 | let functionOutput = "456"; 95 | \`\`\``, 96 | ["functionOutput"], 97 | ); 98 | expect(code).toEqual(null); 99 | }); 100 | it("Code throws error", () => { 101 | const code = parseDataAnalysisResponse(`Thoughts: I am. I think. I will. 102 | 103 | Code: 104 | \`\`\` 105 | throw new Error("This is an error"); 106 | \`\`\``); 107 | expect(code).toEqual({ error: "This is an error" }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /__tests__/lib/v2/edge-runtime/ai.test.ts: -------------------------------------------------------------------------------- 1 | import { hideLongGraphOutputs } from "../../../../lib/v2/edge-runtime/ai"; 2 | 3 | describe("hideLongGraphOutputs", () => { 4 | it("basic", () => { 5 | const out = hideLongGraphOutputs( 6 | [ 7 | { 8 | role: "function", 9 | name: "plot", 10 | content: JSON.stringify({ 11 | type: "bar", 12 | data: new Array(250).fill({ 13 | x: "Attack helicopter", 14 | y: 345769134950.346088724, 15 | badgers: 16 | "are sometimes culled in the country because they have bovine TB (colloquially known as badger pox)", 17 | }), 18 | }), 19 | }, 20 | ], 21 | ["plot"], 22 | ); 23 | expect(out.chatGptPrompt).toEqual([ 24 | { 25 | role: "function", 26 | name: "plot", 27 | content: 28 | JSON.stringify({ 29 | type: "bar", 30 | data: new Array(3).fill({ 31 | x: "Attack helicopter", 32 | y: 345769134950.346088724, 33 | badgers: 34 | "are sometimes culled in the country because they have bovine TB (colloquially known as badger pox)", 35 | }), 36 | }).slice(0, -2) + 37 | ",]}", 38 | }, 39 | ]); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /__tests__/lib/v2/edge-runtime/filter-actions.test.ts: -------------------------------------------------------------------------------- 1 | import { combineSelectedFunctions } from "../../../../lib/v2/edge-runtime/filterActions"; 2 | 3 | describe("combineSelectedFunctions", () => { 4 | it("Picks the thoughts from the most selected functions", () => { 5 | expect( 6 | combineSelectedFunctions( 7 | [ 8 | { selectedFunctions: ["a", "b"], thoughts: "" }, 9 | { selectedFunctions: ["b", "c"], thoughts: "" }, 10 | { 11 | selectedFunctions: ["a", "b", "c"], 12 | thoughts: "This is the one to select", 13 | }, 14 | ], 15 | // @ts-ignore 16 | [{ name: "a" }, { name: "b" }, { name: "c" }], 17 | ), 18 | ).toStrictEqual({ 19 | thoughts: ["This is the one to select", "", ""], 20 | actions: [{ name: "a" }, { name: "b" }, { name: "c" }], 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /__tests__/lib/v2/prompts/routing.test.ts: -------------------------------------------------------------------------------- 1 | import { parseRoutingOutput } from "../../../../lib/v2/prompts/routing"; 2 | 3 | describe("parseRoutingOutput", () => { 4 | it("should return null if the output does not match the expected format", () => { 5 | expect(parseRoutingOutput("", true)).toBeNull(); 6 | }); 7 | it("should return the thoughts and choice if the output matches the expected format", () => { 8 | expect( 9 | parseRoutingOutput( 10 | "Thoughts:\n1. Think step-by-step how to answer the user's request\n2. break down the user's request into steps\n3. specifically name EVERY SINGLE function and variable you will use\n4. consider if the user's request requires searching by name - use DIRECT if so\n5. compare the user's request with CRITERIA\n6. state your choice\n\nChoice: DIRECT\n", 11 | true, 12 | ), 13 | ).toEqual({ 14 | thoughts: 15 | "1. Think step-by-step how to answer the user's request\n2. break down the user's request into steps\n3. specifically name EVERY SINGLE function and variable you will use\n4. consider if the user's request requires searching by name - use DIRECT if so\n5. compare the user's request with CRITERIA\n6. state your choice", 16 | choice: "DIRECT", 17 | }); 18 | }); 19 | it("not finished streaming", () => { 20 | expect( 21 | parseRoutingOutput( 22 | "Thoughts:\n1. Think step-by-step how to answer the user's request\n2. break down the user's request into steps\n3. specifically name EVERY SINGLE function and variable you will use\n4. consider if the user's request requires searching by name - use DIRECT if so\n5. compare the user's request with CRITERIA\n6. state your choice\n\nChoice: DIREC", 23 | true, 24 | ), 25 | ).toBeNull(); 26 | }); 27 | it("not valid answer, but we move", () => { 28 | expect( 29 | parseRoutingOutput( 30 | "Thoughts:\n1. Think step-by-step how to answer the user's request\n2. break down the user's request into steps\n3. specifically name EVERY SINGLE function and variable you will use\n4. consider if the user's request requires searching by name - use DIRECT if so\n5. compare the user's request with CRITERIA\n6. state your choice\n\nChoice: this ", 31 | true, 32 | ), 33 | ).toEqual({ 34 | thoughts: 35 | "1. Think step-by-step how to answer the user's request\n2. break down the user's request into steps\n3. specifically name EVERY SINGLE function and variable you will use\n4. consider if the user's request requires searching by name - use DIRECT if so\n5. compare the user's request with CRITERIA\n6. state your choice", 36 | choice: "this", 37 | }); 38 | }); 39 | it("streaming ended", () => { 40 | expect( 41 | parseRoutingOutput( 42 | "Thoughts:\n1. Think step-by-step how to answer the user's request\n2. break down the user's request into steps\n3. specifically name EVERY SINGLE function and variable you will use\n4. consider if the user's request requires searching by name - use DIRECT if so\n5. compare the user's request with CRITERIA\n6. state your choice\n\nChoice: CODE", 43 | false, 44 | ), 45 | ).toEqual({ 46 | thoughts: 47 | "1. Think step-by-step how to answer the user's request\n2. break down the user's request into steps\n3. specifically name EVERY SINGLE function and variable you will use\n4. consider if the user's request requires searching by name - use DIRECT if so\n5. compare the user's request with CRITERIA\n6. state your choice", 48 | choice: "CODE", 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /__tests__/lib/v2/prompts/summariseChatHistory.test.ts: -------------------------------------------------------------------------------- 1 | import { getChatHistoryText } from "../../../../lib/v2/prompts/summariseChatHistory"; 2 | 3 | describe("summariseChatHistory", () => { 4 | it("", () => { 5 | const historyText = getChatHistoryText([ 6 | { 7 | role: "user", 8 | content: "I want to book a flight to Paris", 9 | }, 10 | { 11 | role: "assistant", 12 | content: "Sure, when would you like to go?", 13 | }, 14 | { 15 | role: "user", 16 | content: "Next week", 17 | }, 18 | { 19 | role: "function", 20 | name: "getChatHistoryText", 21 | content: "I'm sorry, I don't understand", 22 | }, 23 | ]); 24 | expect(historyText).toEqual({ 25 | pastConversation: 26 | "User (oldest): I want to book a flight to Paris\n\nAssistant (most recent): Sure, when would you like to go?", 27 | numPastMessagesIncluded: 3, 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /__tests__/lib/v2/prompts/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { parseTellUser } from "../../../../lib/v2/prompts/utils"; 2 | 3 | describe("parseTellUser", () => { 4 | it("empty", () => { 5 | expect(parseTellUser("")).toEqual(""); 6 | }); 7 | it("no keyword", () => { 8 | expect(parseTellUser("hello")).toEqual("hello"); 9 | }); 10 | it("no keyword, multiple lines", () => { 11 | expect(parseTellUser("hello\nworld")).toEqual("hello\nworld"); 12 | }); 13 | it("Just tell user", () => { 14 | expect(parseTellUser("Tell user: hello")).toEqual("hello"); 15 | }); 16 | it("Other section, no tell user", () => { 17 | expect(parseTellUser("Thoughts: hello")).toEqual("Thoughts: hello"); 18 | }); 19 | it("Other section, tell user", () => { 20 | expect(parseTellUser("Thoughts: hello\nTell user: world")).toEqual("world"); 21 | }); 22 | it("Other section, tell user, multiple lines", () => { 23 | expect(parseTellUser("Thoughts: hello\nTell user: world\nworld")).toEqual( 24 | "world\nworld", 25 | ); 26 | }); 27 | it("Wacky real world example", () => { 28 | const input = `Tell user: 29 | Ava interacted with multiple companies this week. Here is a list of those companies: 30 | 31 | 1. Company A 32 | 2. Company B 33 | 3. Company C 34 | 4. Company D 35 | 5. Company E 36 | 6. Company F 37 | 7. Company G 38 | 8. Company H 39 | 40 | Reasoning: 41 | 1. The coder used the search_engagements function with the specified parameters to retrieve Ava's engagements this week. 42 | 2. For each engagement, they then used the search_contacts function to get the associated contact's information. 43 | 3. However, based on the logs, it appears that there was no data returned for the companies Ava interacted with. 44 | 4. This could be due to a lack of data or an issue with retrieving company information from contact IDs. 45 | 46 | Please note that without specific company names in our database, I am unable to provide you with more detailed information about these interactions at this time. 47 | 48 | If you have any other questions or need further assistance, please let me know!`; 49 | expect(parseTellUser(input)).toEqual( 50 | `Ava interacted with multiple companies this week. Here is a list of those companies: 51 | 52 | 1. Company A 53 | 2. Company B 54 | 3. Company C 55 | 4. Company D 56 | 5. Company E 57 | 6. Company F 58 | 7. Company G 59 | 8. Company H`, 60 | ); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /__tests__/lib/v3/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | convertIsoToHumanReadable, 3 | fillVariables, 4 | } from "../../../lib/v3/utils"; 5 | 6 | describe("convertIsoToHumanReadable", () => { 7 | it("basic", () => { 8 | expect(convertIsoToHumanReadable("2024-01-01")).toEqual("1st January 2024"); 9 | expect(convertIsoToHumanReadable("2024-05-30")).toEqual("30th May 2024"); 10 | }); 11 | }); 12 | 13 | describe("fillVariables", () => { 14 | const noVariableObjs = [ 15 | { 16 | text: "This is some text without variables", 17 | embedded_text: "This is some text without variables", 18 | variable_values: {}, 19 | primary_question: true, 20 | }, 21 | ]; 22 | it("no variables", () => { 23 | expect(fillVariables(noVariableObjs, {}, {})).toEqual(noVariableObjs); 24 | }); 25 | const variableObj = [ 26 | { 27 | text: "This is some text with a variable: {variable1}", 28 | embedded_text: "This is some text with a variable: {variable1}", 29 | variable_values: {}, 30 | primary_question: true, 31 | }, 32 | ]; 33 | it("text with 1 variable", () => { 34 | expect(fillVariables(variableObj, { variable1: "value1" }, {})).toEqual([ 35 | { 36 | text: "This is some text with a variable: {variable1}", 37 | embedded_text: "This is some text with a variable: value1", 38 | variable_values: { variable1: "value1" }, 39 | primary_question: true, 40 | }, 41 | ]); 42 | }); 43 | it("text with 1 variable: embed all", () => { 44 | expect( 45 | fillVariables(variableObj, {}, { variable1: ["value1", "value2"] }), 46 | ).toEqual([ 47 | { 48 | text: "This is some text with a variable: {variable1}", 49 | embedded_text: "This is some text with a variable: value1", 50 | variable_values: { variable1: "value1" }, 51 | primary_question: true, 52 | }, 53 | { 54 | text: "This is some text with a variable: {variable1}", 55 | embedded_text: "This is some text with a variable: value2", 56 | variable_values: { variable1: "value2" }, 57 | primary_question: true, 58 | }, 59 | ]); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /__tests__/prompts/apiMockPrompt.test.ts: -------------------------------------------------------------------------------- 1 | import apiMockPrompt from "../../lib/prompts/apiMock"; 2 | import { Properties } from "../../lib/models"; 3 | 4 | describe("apiMockPrompt", () => { 5 | it("Test object deconstruction works correctly", () => { 6 | const responseType: Properties = { 7 | user: { 8 | type: "string", 9 | description: "The user's name", 10 | path: ["user"], 11 | }, 12 | }; 13 | 14 | const res = apiMockPrompt( 15 | "/very/nice/path", 16 | "GET", 17 | null, 18 | responseType, 19 | undefined, 20 | false, 21 | ); 22 | 23 | const extractedString = res[1].content 24 | .split("Fields\n---")[3] 25 | .split("Response\n---")[0] 26 | .trim(); 27 | 28 | expect(extractedString).toEqual(`user (string): The user's name`); 29 | }); 30 | 31 | it("Test object deconstruction works correctly nested property", () => { 32 | const responseType: Properties = { 33 | "user.id": { 34 | type: "string", 35 | description: "The user's id", 36 | path: ["user, id"], 37 | }, 38 | }; 39 | 40 | const res = apiMockPrompt( 41 | "/very/nice/path", 42 | "GET", 43 | null, 44 | responseType, 45 | undefined, 46 | false, 47 | ); 48 | 49 | const extractedString = res[1].content 50 | .split("Fields\n---")[3] 51 | .split("Response\n---")[0] 52 | .trim(); 53 | 54 | expect(extractedString).toEqual(`user.id (string): The user's id`); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /__tests__/prompts/suggestFollowUps.test.ts: -------------------------------------------------------------------------------- 1 | import { filterConversationForFollowUps } from "../../lib/prompts/suggestFollowUps"; 2 | 3 | describe("filterConversationForFollowUps", () => { 4 | it("Simple", () => { 5 | const out = filterConversationForFollowUps([ 6 | { 7 | role: "user", 8 | content: "Hello", 9 | }, 10 | ]); 11 | expect(out).toEqual([ 12 | { 13 | role: "assistant", 14 | content: "Hello", 15 | }, 16 | ]); 17 | }); 18 | it("Simple 2", () => { 19 | const out = filterConversationForFollowUps([ 20 | { 21 | role: "user", 22 | content: "Hello", 23 | }, 24 | { 25 | role: "assistant", 26 | content: "Hi", 27 | }, 28 | ]); 29 | expect(out).toEqual([ 30 | { 31 | role: "assistant", 32 | content: "Hello", 33 | }, 34 | { 35 | role: "user", 36 | content: "Hi", 37 | }, 38 | ]); 39 | }); 40 | it("Actually requires filtering", () => { 41 | const out = filterConversationForFollowUps([ 42 | { 43 | role: "user", 44 | content: "Hello", 45 | }, 46 | { 47 | role: "assistant", 48 | content: "Hi", 49 | }, 50 | { 51 | role: "function", 52 | name: "summarizeText", 53 | content: "This is a summary", 54 | }, 55 | { 56 | role: "assistant", 57 | content: "I returned a summary", 58 | }, 59 | ]); 60 | expect(out).toEqual([ 61 | { 62 | role: "assistant", 63 | content: "Hello", 64 | }, 65 | { 66 | role: "user", 67 | content: "I returned a summary", 68 | }, 69 | ]); 70 | }); 71 | it("Requires filtering 2", () => { 72 | const out = filterConversationForFollowUps([ 73 | { 74 | role: "user", 75 | content: "Hello", 76 | }, 77 | { 78 | role: "assistant", 79 | content: "Hi", 80 | }, 81 | { 82 | role: "function", 83 | name: "summarizeText", 84 | content: "This is a summary", 85 | }, 86 | { 87 | role: "assistant", 88 | content: "I returned a summary", 89 | }, 90 | { 91 | role: "user", 92 | content: "Thanks for that", 93 | }, 94 | { 95 | role: "assistant", 96 | content: "What is going on???", 97 | }, 98 | { 99 | role: "function", 100 | name: "summarizeText", 101 | content: "This shouldn't be shown!!!", 102 | }, 103 | { 104 | role: "assistant", 105 | content: "You're welcome", 106 | }, 107 | ]); 108 | expect(out).toEqual([ 109 | { 110 | role: "assistant", 111 | content: "Hello", 112 | }, 113 | { 114 | role: "user", 115 | content: "I returned a summary", 116 | }, 117 | { 118 | role: "assistant", 119 | content: "Thanks for that", 120 | }, 121 | { 122 | role: "user", 123 | content: "You're welcome", 124 | }, 125 | ]); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /__tests__/prompts/summarizeText.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "@jest/globals"; 2 | import { getSummarizeTextPrompt } from "../../lib/prompts/summarizeText"; 3 | 4 | describe("getSummarizeTextPrompt", () => { 5 | it("basic prompt", () => { 6 | // @ts-ignore 7 | const summarizePrompt = getSummarizeTextPrompt("This is a test", { 8 | name: "test", 9 | description: "test description", 10 | }); 11 | expect(summarizePrompt).toEqual([ 12 | { 13 | role: "system", 14 | content: `Your task is to summarize a document using bullet points and short simple sentences. 15 | 16 | For context of what to include in your summary, you are working for test. test description 17 | 18 | Your response should be a maximum of 6 bullet points. Only use three, four or five bullet points if you can. Only write short simple sentences. Only write one sentence per bullet point. Be as succinct as possible. 19 | 20 | Include ALL numbers and statistics. THIS IS VERY IMPORTANT! DO NOT FORGET THIS! YOU SHOULD BE PARANOID THAT YOU MIGHT FORGET THIS! 21 | 22 | DO NOT include legal disclaimers, privacy policies or copyright information. DO NOT FORGET THIS! 23 | 24 | Your summary should follow this format: 25 | - Bullet 1 26 | - Bullet 2 27 | 28 | The user's message will be the text to summarize. 29 | 30 | Total word limit of the summary: 200 words`, 31 | }, 32 | { 33 | role: "user", 34 | content: "This is a test", 35 | }, 36 | ]); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /__tests__/swagger-to-actions.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "@jest/globals"; 2 | import { 3 | operationIdToFunctionName, 4 | replaceMarkdownLinks, 5 | requestToFunctionName, 6 | } from "../pages/api/swagger-to-actions"; 7 | 8 | describe("request to function name", () => { 9 | it("list organizations", () => { 10 | const fnName = requestToFunctionName("get", {}, "/api/v1/organizations"); 11 | expect(fnName).toEqual("list_organizations"); 12 | }); 13 | it("get organization by id", () => { 14 | const fnName = requestToFunctionName( 15 | "get", 16 | { parameters: [{ in: "path", name: "id" }] }, 17 | "/api/v1/organizations/{id}", 18 | ); 19 | expect(fnName).toEqual("get_organization_by_id"); 20 | }); 21 | it("create chat completion", () => { 22 | const fnName = requestToFunctionName("post", {}, "/chat/completions"); 23 | expect(fnName).toEqual("create_chat_completion"); 24 | }); 25 | it("get by parent_lookup_organization_id domain", () => { 26 | const fnName = requestToFunctionName( 27 | "get", 28 | { parameters: [{ in: "path", name: "parent_lookup_organization_id" }] }, 29 | "/api/organizations/{parent_lookup_organization_id}/domains/", 30 | ); 31 | expect(fnName).toEqual("get_by_parent_lookup_organization_id_domain"); 32 | }); 33 | it("get_plugin_activity", () => { 34 | const fnName = requestToFunctionName( 35 | "get", 36 | { parameters: [{ in: "path", name: "parent_lookup_organization_id" }] }, 37 | "/api/organizations/{parent_lookup_organization_id}/plugins/activity/", 38 | ); 39 | expect(fnName).toEqual("get_plugin_activity"); 40 | }); 41 | }); 42 | 43 | describe("replaceMarkdownLinks", () => { 44 | it("undefined passes through", () => { 45 | let result = replaceMarkdownLinks(undefined); 46 | expect(result).toEqual(undefined); 47 | }); 48 | it("replace 1 markdown link", () => { 49 | let testString = "there's [something](https://something.com)"; 50 | let result = replaceMarkdownLinks(testString); 51 | expect(result).toEqual("there's something"); 52 | }); 53 | it("replace 2 markdown links", () => { 54 | let testString = 55 | "there's [something](https://something.com) [here](https://something.com)"; 56 | let result = replaceMarkdownLinks(testString); 57 | expect(result).toEqual("there's something here"); 58 | }); 59 | }); 60 | 61 | describe("operationIdToFunctionName", () => { 62 | it("handles simple camel case", () => { 63 | expect(operationIdToFunctionName("camelCase")).toBe("camel_case"); 64 | }); 65 | 66 | it("handles multiple capital letters in a row", () => { 67 | expect(operationIdToFunctionName("camelCaseJSON")).toBe("camel_case_json"); 68 | }); 69 | 70 | it("handles words without capital letters", () => { 71 | expect(operationIdToFunctionName("camelcase")).toBe("camelcase"); 72 | }); 73 | 74 | it("handles single words", () => { 75 | expect(operationIdToFunctionName("Camel")).toBe("camel"); 76 | }); 77 | 78 | it("handles words with numbers", () => { 79 | expect(operationIdToFunctionName("camelCase2Go")).toBe("camel_case2_go"); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /__tests__/testActions.ts: -------------------------------------------------------------------------------- 1 | import { Action } from "../lib/types"; 2 | 3 | export const testActions: Action[] = [ 4 | { 5 | tag: 1, 6 | action_type: "http", 7 | description: "", 8 | active: true, 9 | created_at: "2022-01-01T00:00:00Z", 10 | id: 1, 11 | keys_to_keep: null, 12 | name: "action_a", 13 | org_id: 1, 14 | parameters: null, 15 | path: "/api/v1/endpoint1", 16 | request_body_contents: null, 17 | request_method: "POST", 18 | responses: null, 19 | api_id: "12345", 20 | requires_confirmation: false, 21 | link_url: "", 22 | link_name: "", 23 | }, 24 | { 25 | tag: 1, 26 | action_type: "http", 27 | description: "", 28 | active: true, 29 | created_at: "2022-01-01T00:00:00Z", 30 | id: 2, 31 | keys_to_keep: null, 32 | name: "action_b", 33 | org_id: 1, 34 | parameters: null, 35 | path: "/api/v2/endpoint2", 36 | request_body_contents: null, 37 | request_method: "GET", 38 | responses: null, 39 | api_id: "12345", 40 | requires_confirmation: false, 41 | link_url: "", 42 | link_name: "", 43 | }, 44 | { 45 | tag: 1, 46 | action_type: "http", 47 | description: "", 48 | active: true, 49 | created_at: "2022-01-01T00:00:00Z", 50 | id: 3, 51 | keys_to_keep: null, 52 | name: "action_c", 53 | org_id: 1, 54 | parameters: null, 55 | path: "/api/v2/endpoint3", 56 | request_body_contents: null, 57 | request_method: "PUT", 58 | responses: null, 59 | api_id: "12345", 60 | requires_confirmation: false, 61 | link_url: "", 62 | link_name: "", 63 | }, 64 | { 65 | tag: 1, 66 | action_type: "http", 67 | description: "", 68 | active: true, 69 | created_at: "2022-01-01T00:00:00Z", 70 | id: 3, 71 | keys_to_keep: null, 72 | name: "action_d", 73 | org_id: 1, 74 | parameters: null, 75 | path: "/api/v2/endpoint3/{id}", 76 | request_body_contents: null, 77 | request_method: "PUT", 78 | responses: null, 79 | api_id: "12345", 80 | requires_confirmation: false, 81 | link_url: "", 82 | link_name: "", 83 | }, 84 | { 85 | tag: 1, 86 | action_type: "http", 87 | description: "", 88 | active: true, 89 | created_at: "2022-01-01T00:00:00Z", 90 | id: 3, 91 | keys_to_keep: null, 92 | name: "action_e", 93 | org_id: 1, 94 | parameters: null, 95 | path: "/api/v2/{id}/endpoint3", 96 | request_body_contents: null, 97 | request_method: "PUT", 98 | responses: null, 99 | api_id: "12345", 100 | requires_confirmation: false, 101 | link_url: "", 102 | link_name: "", 103 | }, 104 | { 105 | tag: 1, 106 | action_type: "http", 107 | description: "", 108 | active: true, 109 | created_at: "2022-01-01T00:00:00Z", 110 | id: 3, 111 | keys_to_keep: null, 112 | name: "action_f", 113 | org_id: 1, 114 | parameters: null, 115 | path: "/api/0/teams/{organization_slug}/{team_slug}/", 116 | request_body_contents: null, 117 | request_method: "PUT", 118 | responses: null, 119 | api_id: "12345", 120 | requires_confirmation: false, 121 | link_url: "", 122 | link_name: "", 123 | }, 124 | ]; 125 | -------------------------------------------------------------------------------- /__tests__/testData/Henry-Pulver-CV.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Superflows-AI/superflows/4c8c40c05fa7ba812f1a0c78d00be61ca932b93c/__tests__/testData/Henry-Pulver-CV.pdf -------------------------------------------------------------------------------- /__tests__/testData/LOA_superflows-sign-in_413.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Superflows-AI/superflows/4c8c40c05fa7ba812f1a0c78d00be61ca932b93c/__tests__/testData/LOA_superflows-sign-in_413.pdf -------------------------------------------------------------------------------- /__tests__/utils/isDate.test.ts: -------------------------------------------------------------------------------- 1 | import { isDate } from "../../lib/utils"; 2 | 3 | describe("isDate", () => { 4 | it("date ISO with Z format", () => { 5 | expect(isDate("2023-04-13T21:21:00Z")).toBeTruthy(); 6 | }); 7 | it("date ISO with Z format and milliseconds", () => { 8 | expect(isDate("2023-04-13T21:21:00.475Z")).toBeTruthy(); 9 | }); 10 | it("date ISO with milliseconds", () => { 11 | expect(isDate("2023-04-13T21:21:00.475")).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /__tests__/utils/isURL.test.ts: -------------------------------------------------------------------------------- 1 | import { isUrl } from "../../lib/utils"; 2 | 3 | const validUrls = [ 4 | "x.com", 5 | "www.google.com", 6 | "https://www.google.com", 7 | "http://www.google.com", 8 | "https://google.com", 9 | "https://google.com/search", 10 | "https://google.com/search?q=hello", 11 | "https://google.com/search?q=hello&user=me", 12 | "https://google.com/search/?q=hello&user=me", 13 | "https://google.com/search/#hello", 14 | "https://google.com/search/hello.html", 15 | "https://google.com/search/hello.html?user=me", 16 | "https://google.com/search/hello.html?user=me#hello", 17 | ]; 18 | 19 | const validUrlsWithIDX = [ 20 | "x.com/ID1", 21 | "www.google.com/ID7", 22 | "https://www.google.com?id=ID22", 23 | ]; 24 | 25 | const invalidUrls = [ 26 | "henry@gmail.com", 27 | "183bd6ff-e8fd-44a6-a3a8-eed9cb1082df", 28 | "This isn't a URL", 29 | "James Rowland", 30 | "alphanumericStringNoNumbers", 31 | "alphanumeric1With2Numbers", 32 | ]; 33 | 34 | describe("isURL", () => { 35 | it("should return true for valid URLs", () => { 36 | for (const url of validUrls) { 37 | expect(isUrl(url)).toBe(true); 38 | } 39 | }); 40 | it("should return false for invalid URLs", () => { 41 | for (const url of invalidUrls) { 42 | expect(isUrl(url)).toBe(false); 43 | } 44 | }); 45 | it("Not a URL if it has an IDX in it", () => { 46 | for (const url of validUrlsWithIDX) { 47 | expect(isUrl(url)).toBe(false); 48 | } 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /__tests__/utils/joinArraysNoDuplicates.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "@jest/globals"; 2 | import { joinArraysNoDuplicates } from "../../lib/utils"; 3 | 4 | describe("Tests for joinArrayWithoutDuplication function", () => { 5 | it("Checks if function returns joined arrays without duplications", () => { 6 | const firstArray = [ 7 | { id: 1, name: "John" }, 8 | { id: 2, name: "Paul" }, 9 | ]; 10 | const secondArray = [ 11 | { id: 3, name: "George" }, 12 | { id: 4, name: "Ringo" }, 13 | ]; 14 | const expected = [ 15 | { id: 1, name: "John" }, 16 | { id: 2, name: "Paul" }, 17 | { id: 3, name: "George" }, 18 | { id: 4, name: "Ringo" }, 19 | ]; 20 | 21 | expect(joinArraysNoDuplicates(firstArray, secondArray, "id")).toEqual( 22 | expected, 23 | ); 24 | }); 25 | it("Checks if function returns joined arrays with duplications", () => { 26 | const firstArray = [ 27 | { id: 1, name: "John" }, 28 | { id: 2, name: "Paul" }, 29 | { id: 3, name: "George" }, 30 | ]; 31 | const secondArray = [ 32 | { id: 2, name: "Paul" }, 33 | { id: 3, name: "George" }, 34 | { id: 4, name: "Ringo" }, 35 | ]; 36 | const expected = [ 37 | { id: 1, name: "John" }, 38 | { id: 2, name: "Paul" }, 39 | { id: 3, name: "George" }, 40 | { id: 4, name: "Ringo" }, 41 | ]; 42 | 43 | expect(joinArraysNoDuplicates(firstArray, secondArray, "id")).toEqual( 44 | expected, 45 | ); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /__tests__/utils/stripTrailingAndCurly.test.ts: -------------------------------------------------------------------------------- 1 | import { stripTrailingAndCurly } from "../../lib/utils"; 2 | import { describe, expect, it } from "@jest/globals"; 3 | 4 | describe("stripTrailingAndCurly", () => { 5 | it("should remove nothing", () => { 6 | expect(stripTrailingAndCurly("/api")).toBe("/api"); 7 | }); 8 | 9 | it("should remove trailing slashes", () => { 10 | expect(stripTrailingAndCurly("/api/")).toBe("/api"); 11 | }); 12 | 13 | it("should remove curly brackets", () => { 14 | expect(stripTrailingAndCurly("/api/{id}")).toBe("/api"); 15 | }); 16 | 17 | it("should remove trailing slashes and curly brackets", () => { 18 | expect(stripTrailingAndCurly("/api/{id}/")).toBe("/api"); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /components/actions/consts.ts: -------------------------------------------------------------------------------- 1 | export const exampleParameters = [ 2 | { 3 | in: "query", 4 | name: "name", 5 | description: "Name of the company", 6 | required: true, 7 | schema: { 8 | type: "string", 9 | enum: ["Google", "Facebook", "Apple"], 10 | }, 11 | }, 12 | ]; 13 | 14 | export const exampleRequestBody = { 15 | "application/json": { 16 | schema: { 17 | required: ["name"], 18 | type: "object", 19 | properties: { 20 | name: { 21 | type: "string", 22 | enum: ["Google", "Facebook", "Apple"], 23 | }, 24 | }, 25 | }, 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /components/approval/question.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function QuestionText(props: { questionText: string }) { 4 | return ( 5 |

6 | {props.questionText 7 | .split(/(\{\w+})/g) 8 | .map((part: string, idx: number) => { 9 | if (part.match(/\{(\w+)}/)) { 10 | return ( 11 | 12 | {part} 13 | 14 | ); 15 | } else { 16 | return {part}; 17 | } 18 | })} 19 |

20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /components/approval/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ApprovalAnswer, 3 | ApprovalAnswerMessage, 4 | ApprovalQuestion, 5 | } from "../../lib/types"; 6 | 7 | export type UIAnswerType = Omit< 8 | ApprovalAnswer, 9 | "created_at" | "description" | "fnName" | "org_id" | "is_docs" 10 | > & { 11 | approval_questions: Pick[]; 12 | }; 13 | 14 | export type UIMessageData = Pick< 15 | ApprovalAnswerMessage, 16 | "id" | "raw_text" | "message_idx" | "message_type" | "generated_output" 17 | >; 18 | -------------------------------------------------------------------------------- /components/autoGrowingTextarea.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | 3 | export function AutoGrowingTextArea(props: { 4 | className: string; 5 | placeholder: string; 6 | value: string; 7 | onChange: (e: React.ChangeEvent) => void; 8 | onKeyDown?: (e: React.KeyboardEvent) => void; 9 | minHeight?: number; 10 | maxHeight?: number; 11 | onBlur?: React.FocusEventHandler; 12 | disabled?: boolean; 13 | ref?: React.RefObject; 14 | }) { 15 | const localRef = useRef(null); 16 | const ref = props.ref ?? localRef; 17 | 18 | useEffect(() => { 19 | if (ref.current === null) return; 20 | // @ts-ignore 21 | ref.current.style.height = "5px"; 22 | 23 | let maxH = props.maxHeight ?? 500; 24 | let minH = props.minHeight ?? 0; 25 | 26 | // @ts-ignore 27 | ref.current.style.height = 28 | // @ts-ignore 29 | Math.max(Math.min(ref.current.scrollHeight, maxH), minH) + "px"; 30 | }, [ref.current, props.value]); 31 | 32 | return ( 33 |